LCOV - code coverage report
Current view: top level - src/engraving/dom - ambitus.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 29.8 % 262 78
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 "ambitus.h"
      24              : 
      25              : #include "translation.h"
      26              : 
      27              : #include "accidental.h"
      28              : #include "chord.h"
      29              : #include "factory.h"
      30              : #include "measure.h"
      31              : #include "note.h"
      32              : #include "score.h"
      33              : #include "segment.h"
      34              : #include "staff.h"
      35              : #include "system.h"
      36              : #include "utils.h"
      37              : 
      38              : #include "log.h"
      39              : 
      40              : using namespace mu;
      41              : using namespace mu::engraving;
      42              : 
      43              : //---------------------------------------------------------
      44              : //   Ambitus
      45              : //---------------------------------------------------------
      46              : 
      47            2 : Ambitus::Ambitus(DummyParentOr<Segment> parent)
      48            2 :     : EngravingItem(ElementType::AMBITUS, parent, ElementFlag::ON_STAFF)
      49              : {
      50            2 :     m_noteHeadGroup    = NOTEHEADGROUP_DEFAULT;
      51            2 :     m_noteHeadType     = NOTEHEADTYPE_DEFAULT;
      52            2 :     m_direction        = DIRECTION_DEFAULT;
      53            2 :     m_hasLine          = HASLINE_DEFAULT;
      54            2 :     m_lineWidth        = LINEWIDTH_DEFAULT;
      55            2 :     m_topPitch         = INVALID_PITCH;
      56            2 :     m_bottomPitch      = INVALID_PITCH;
      57            2 :     m_topTpc           = Tpc::TPC_INVALID;
      58            2 :     m_bottomTpc        = Tpc::TPC_INVALID;
      59              : 
      60            2 :     m_topAccidental = Factory::createAccidental(this, false);
      61            2 :     m_bottomAccidental = Factory::createAccidental(this, false);
      62            2 : }
      63              : 
      64            0 : Ambitus::Ambitus(const Ambitus& a)
      65            0 :     : EngravingItem(a)
      66              : {
      67            0 :     m_noteHeadGroup = a.m_noteHeadGroup;
      68            0 :     m_noteHeadType = a.m_noteHeadType;
      69            0 :     m_direction = a.m_direction;
      70            0 :     m_hasLine = a.m_hasLine;
      71            0 :     m_lineWidth = a.m_lineWidth;
      72              : 
      73            0 :     m_topAccidental = a.m_topAccidental->clone();
      74            0 :     m_topAccidental->setOwnershipParent(this);
      75              : 
      76            0 :     m_bottomAccidental = a.m_bottomAccidental->clone();
      77            0 :     m_bottomAccidental->setOwnershipParent(this);
      78              : 
      79            0 :     m_topPitch = a.m_topPitch;
      80            0 :     m_topTpc = a.m_topTpc;
      81            0 :     m_bottomPitch = a.m_bottomPitch;
      82            0 :     m_bottomTpc = a.m_bottomTpc;
      83            0 : }
      84              : 
      85            4 : Ambitus::~Ambitus()
      86              : {
      87            2 :     delete m_topAccidental;
      88            2 :     delete m_bottomAccidental;
      89            4 : }
      90              : 
      91              : //---------------------------------------------------------
      92              : //   mag
      93              : //---------------------------------------------------------
      94              : 
      95           32 : double Ambitus::mag() const
      96              : {
      97           32 :     return staff() ? staff()->staffMag(tick()) : 1.0;
      98              : }
      99              : 
     100              : //---------------------------------------------------------
     101              : //   initFrom
     102              : //---------------------------------------------------------
     103              : 
     104            0 : void Ambitus::initFrom(Ambitus* a)
     105              : {
     106            0 :     m_noteHeadGroup   = a->m_noteHeadGroup;
     107            0 :     m_noteHeadType    = a->m_noteHeadType;
     108            0 :     m_direction       = a->m_direction;
     109            0 :     m_hasLine         = a->m_hasLine;
     110            0 :     m_lineWidth       = a->m_lineWidth;
     111            0 :     m_topPitch        = a->m_topPitch;
     112            0 :     m_bottomPitch     = a->m_bottomPitch;
     113            0 :     m_topTpc          = a->m_topTpc;
     114            0 :     m_bottomTpc       = a->m_bottomTpc;
     115            0 : }
     116              : 
     117              : //---------------------------------------------------------
     118              : //   setTrack
     119              : //
     120              : //    when the Ambitus element is assigned a track,
     121              : //    initialize top and bottom 'notes' to top and bottom staff lines
     122              : //---------------------------------------------------------
     123              : 
     124            2 : void Ambitus::setTrack(track_idx_t t)
     125              : {
     126            2 :     EngravingItem::setTrack(t);
     127              : 
     128              :     // if not initialized and there is a segment and a staff,
     129              :     // initialize pitches and tpc's to first and last staff line
     130              :     // (for use in palettes)
     131            4 :     if (!pitchIsValid(m_topPitch) || !tpcIsValid(m_topTpc)
     132            4 :         || !pitchIsValid(m_bottomPitch) || !tpcIsValid(m_bottomTpc)) {
     133            0 :         if (segment() && staff()) {
     134            0 :             Ambitus::Ranges ranges = estimateRanges();
     135            0 :             m_topTpc = ranges.topTpc;
     136            0 :             m_bottomTpc = ranges.bottomTpc;
     137            0 :             m_topPitch = ranges.topPitch;
     138            0 :             m_bottomPitch = ranges.bottomPitch;
     139              : 
     140            0 :             m_topAccidental->setTrack(t);
     141            0 :             m_bottomAccidental->setTrack(t);
     142              :         }
     143              :     }
     144            2 : }
     145              : 
     146              : //---------------------------------------------------------
     147              : //   setTop/BottomPitch
     148              : //
     149              : //    setting either pitch requires to adjust the corresponding tpc
     150              : //---------------------------------------------------------
     151              : 
     152            2 : void Ambitus::setTopPitch(int val, bool applyLogic)
     153              : {
     154            2 :     if (!applyLogic) {
     155            2 :         m_topPitch = val;
     156            2 :         return;
     157              :     }
     158              : 
     159              :     // if pitch difference is not an integer number of octaves, adjust tpc
     160              :     // (to avoid 'wild' tpc changes with octave changes)
     161            0 :     if ((val - topPitch()) % PITCH_DELTA_OCTAVE != 0) {
     162            0 :         Key key = (staff() && segment()) ? staff()->key(segment()->tick()) : Key::C;
     163            0 :         m_topTpc = pitch2tpc(val, key, Prefer::NEAREST);
     164              :     }
     165            0 :     m_topPitch = val;
     166            0 :     normalize();
     167              : }
     168              : 
     169            2 : void Ambitus::setBottomPitch(int val, bool applyLogic)
     170              : {
     171            2 :     if (!applyLogic) {
     172            2 :         m_bottomPitch = val;
     173            2 :         return;
     174              :     }
     175              : 
     176              :     // if pitch difference is not an integer number of octaves, adjust tpc
     177              :     // (to avoid 'wild' tpc changes with octave changes)
     178            0 :     if ((val - bottomPitch()) % PITCH_DELTA_OCTAVE != 0) {
     179            0 :         Key key = (staff() && segment()) ? staff()->key(segment()->tick()) : Key::C;
     180            0 :         m_bottomTpc = pitch2tpc(val, key, Prefer::NEAREST);
     181              :     }
     182            0 :     m_bottomPitch = val;
     183            0 :     normalize();
     184              : }
     185              : 
     186              : //---------------------------------------------------------
     187              : //   setTop/BottomTpc
     188              : //
     189              : //    setting either tpc requires to adjust the corresponding pitch
     190              : //    (but remaining in the same octave)
     191              : //---------------------------------------------------------
     192              : 
     193            2 : void Ambitus::setTopTpc(int val, bool applyLogic)
     194              : {
     195            2 :     m_topTpc = val;
     196              : 
     197            2 :     if (!applyLogic) {
     198            2 :         return;
     199              :     }
     200              : 
     201            0 :     int octave = topPitch() / PITCH_DELTA_OCTAVE;
     202            0 :     int newOctavedPitch = (tpc2pitch(val) + PITCH_DELTA_OCTAVE) % PITCH_DELTA_OCTAVE;
     203            0 :     m_topPitch = (octave * PITCH_DELTA_OCTAVE) + newOctavedPitch;
     204            0 :     normalize();
     205              : }
     206              : 
     207            2 : void Ambitus::setBottomTpc(int val, bool applyLogic)
     208              : {
     209            2 :     m_bottomTpc = val;
     210              : 
     211            2 :     if (!applyLogic) {
     212            2 :         return;
     213              :     }
     214              : 
     215            0 :     int octave = bottomPitch() / PITCH_DELTA_OCTAVE;
     216            0 :     int newOctavedPitch = (tpc2pitch(val) + PITCH_DELTA_OCTAVE) % PITCH_DELTA_OCTAVE;
     217            0 :     m_bottomPitch = (octave * PITCH_DELTA_OCTAVE) + newOctavedPitch;
     218            0 :     normalize();
     219              : }
     220              : 
     221              : //---------------------------------------------------------
     222              : //   scanElements
     223              : //---------------------------------------------------------
     224              : 
     225            0 : void Ambitus::scanElements(std::function<void(EngravingItem*)> func)
     226              : {
     227            0 :     func(this);
     228            0 :     if (m_topAccidental->accidentalType() != AccidentalType::NONE) {
     229            0 :         func(m_topAccidental);
     230              :     }
     231              : 
     232            0 :     if (m_bottomAccidental->accidentalType() != AccidentalType::NONE) {
     233            0 :         func(m_bottomAccidental);
     234              :     }
     235            0 : }
     236              : 
     237              : //---------------------------------------------------------
     238              : //   noteHead
     239              : //---------------------------------------------------------
     240              : 
     241            2 : SymId Ambitus::noteHead() const
     242              : {
     243            2 :     int hg = 1;
     244            2 :     NoteHeadType ht  = NoteHeadType::HEAD_QUARTER;
     245              : 
     246            2 :     if (m_noteHeadType != NoteHeadType::HEAD_AUTO) {
     247            1 :         ht = m_noteHeadType;
     248              :     }
     249              : 
     250            2 :     SymId t = Note::noteHead(hg, m_noteHeadGroup, ht);
     251            2 :     if (t == SymId::noSym) {
     252            0 :         LOGD("invalid notehead %d/%d", int(m_noteHeadGroup), int(m_noteHeadType));
     253            0 :         t = Note::noteHead(0, NoteHeadGroup::HEAD_NORMAL, ht);
     254              :     }
     255            2 :     return t;
     256              : }
     257              : 
     258              : //---------------------------------------------------------
     259              : //   headWidth
     260              : //
     261              : //    returns the width of the notehead symbol
     262              : //---------------------------------------------------------
     263              : 
     264            2 : double Ambitus::headWidth() const
     265              : {
     266            2 :     return symWidth(noteHead());
     267              : }
     268              : 
     269              : //---------------------------------------------------------
     270              : //   normalize
     271              : //
     272              : //    makes sure topPitch is not < bottomPitch
     273              : //---------------------------------------------------------
     274              : 
     275            0 : void Ambitus::normalize()
     276              : {
     277            0 :     if (m_topPitch < m_bottomPitch) {
     278            0 :         std::swap(m_topPitch, m_bottomPitch);
     279            0 :         std::swap(m_topTpc, m_bottomTpc);
     280              :     }
     281            0 : }
     282              : 
     283              : //---------------------------------------------------------
     284              : //   updateRange
     285              : //
     286              : //    scans the staff contents up to next section break to update the range pitches/tpc's
     287              : //---------------------------------------------------------
     288              : 
     289            0 : Ambitus::Ranges Ambitus::estimateRanges() const
     290              : {
     291            0 :     Ambitus::Ranges result;
     292              : 
     293            0 :     Segment* s = segment() ? segment()->measure()->findSegment(SegmentType::ChordRest, segment()->tick()) : nullptr;
     294            0 :     for (; s && !s->measure()->sectionBreak(); s = s->nextCR()) {
     295            0 :         for (track_idx_t t = track(); t < track() + VOICES; t++) {
     296            0 :             EngravingItem* e = s->element(t);
     297            0 :             if (!e || !e->isChord()) {
     298            0 :                 continue;
     299              :             }
     300            0 :             Chord* chord = toChord(e);
     301            0 :             for (Note* n : chord->notes()) {
     302            0 :                 if (!n->play()) {
     303            0 :                     continue;
     304              :                 }
     305            0 :                 int pitch = n->epitch() + n->ottaveCapoFret(); // written pitch, accounting for octave offset
     306            0 :                 if (pitch > result.topPitch) {
     307            0 :                     result.topPitch = pitch;
     308            0 :                     result.topTpc   = n->tpc();
     309              :                 }
     310            0 :                 if (pitch < result.bottomPitch) {
     311            0 :                     result.bottomPitch = pitch;
     312            0 :                     result.bottomTpc   = n->tpc();
     313              :                 }
     314              :             }
     315              :         }
     316              :     }
     317              : 
     318            0 :     return result;
     319              : }
     320              : 
     321            0 : void Ambitus::remove(EngravingItem* e)
     322              : {
     323            0 :     if (e->isAccidental()) {
     324              :         //! NOTE Do nothing (removing _topAccid or _bottomAccid)
     325            0 :         return;
     326              :     }
     327              : 
     328            0 :     EngravingItem::remove(e);
     329              : }
     330              : 
     331              : //---------------------------------------------------------
     332              : //   getProperty
     333              : //---------------------------------------------------------
     334              : 
     335           18 : PropertyValue Ambitus::getProperty(Pid propertyId) const
     336              : {
     337           18 :     switch (propertyId) {
     338            0 :     case Pid::HEAD_GROUP:
     339            0 :         return noteHeadGroup();
     340            0 :     case Pid::HEAD_TYPE:
     341            0 :         return noteHeadType();
     342            0 :     case Pid::MIRROR_HEAD:
     343            0 :         return direction();
     344            0 :     case Pid::GHOST:                         // recycled property = _hasLine
     345            0 :         return hasLine();
     346            0 :     case Pid::LINE_WIDTH:
     347            0 :         return lineWidth();
     348            0 :     case Pid::TPC1:
     349            0 :         return topTpc();
     350            0 :     case Pid::FBPARENTHESIS1:                // recycled property = _bottomTpc
     351            0 :         return bottomTpc();
     352            0 :     case Pid::PITCH:
     353            0 :         return topPitch();
     354            0 :     case Pid::FBPARENTHESIS2:                // recycled property = _bottomPitch
     355            0 :         return bottomPitch();
     356            0 :     case Pid::FBPARENTHESIS3:                // recycled property = octave of _topPitch
     357            0 :         return topOctave();
     358            0 :     case Pid::FBPARENTHESIS4:                // recycled property = octave of _bottomPitch
     359            0 :         return bottomOctave();
     360           18 :     default:
     361           18 :         return EngravingItem::getProperty(propertyId);
     362              :     }
     363              : }
     364              : 
     365              : //---------------------------------------------------------
     366              : //   setProperty
     367              : //---------------------------------------------------------
     368              : 
     369            1 : bool Ambitus::setProperty(Pid propertyId, const PropertyValue& v)
     370              : {
     371            1 :     switch (propertyId) {
     372            0 :     case Pid::HEAD_GROUP:
     373            0 :         setNoteHeadGroup(v.value<NoteHeadGroup>());
     374            0 :         break;
     375            0 :     case Pid::HEAD_TYPE:
     376            0 :         setNoteHeadType(v.value<NoteHeadType>());
     377            0 :         break;
     378            1 :     case Pid::MIRROR_HEAD:
     379            1 :         setDirection(v.value<DirectionH>());
     380            1 :         break;
     381            0 :     case Pid::GHOST:                         // recycled property = _hasLine
     382            0 :         setHasLine(v.toBool());
     383            0 :         break;
     384            0 :     case Pid::LINE_WIDTH:
     385            0 :         setLineWidth(v.value<Spatium>());
     386            0 :         break;
     387            0 :     case Pid::TPC1:
     388            0 :         setTopTpc(v.toInt());
     389            0 :         break;
     390            0 :     case Pid::FBPARENTHESIS1:                // recycled property = _bottomTpc
     391            0 :         setBottomTpc(v.toInt());
     392            0 :         break;
     393            0 :     case Pid::PITCH:
     394            0 :         setTopPitch(v.toInt());
     395            0 :         break;
     396            0 :     case Pid::FBPARENTHESIS2:                // recycled property = _bottomPitch
     397            0 :         setBottomPitch(v.toInt());
     398            0 :         break;
     399            0 :     case Pid::FBPARENTHESIS3:                // recycled property = octave of _topPitch
     400            0 :         setTopPitch(topPitch() % PITCH_DELTA_OCTAVE + (v.toInt() + 1) * PITCH_DELTA_OCTAVE);
     401            0 :         break;
     402            0 :     case Pid::FBPARENTHESIS4:                // recycled property = octave of _bottomPitch
     403            0 :         setBottomPitch(bottomPitch() % PITCH_DELTA_OCTAVE + (v.toInt() + 1) * PITCH_DELTA_OCTAVE);
     404            0 :         break;
     405            0 :     case Pid::COLOR:
     406              :     case Pid::VISIBLE:
     407            0 :         m_topAccidental->setProperty(propertyId, v);
     408            0 :         m_bottomAccidental->setProperty(propertyId, v);
     409            0 :         EngravingItem::setProperty(propertyId, v);
     410            0 :         break;
     411            0 :     default:
     412            0 :         return EngravingItem::setProperty(propertyId, v);
     413              :     }
     414            1 :     triggerLayout();
     415            1 :     return true;
     416              : }
     417              : 
     418              : //---------------------------------------------------------
     419              : //   propertyDefault
     420              : //---------------------------------------------------------
     421              : 
     422           18 : PropertyValue Ambitus::propertyDefault(Pid id) const
     423              : {
     424           18 :     switch (id) {
     425            0 :     case Pid::HEAD_GROUP:
     426            0 :         return NOTEHEADGROUP_DEFAULT;
     427            0 :     case Pid::HEAD_TYPE:
     428            0 :         return NOTEHEADTYPE_DEFAULT;
     429            0 :     case Pid::MIRROR_HEAD:
     430            0 :         return DIRECTION_DEFAULT;
     431            0 :     case Pid::GHOST:
     432            0 :         return HASLINE_DEFAULT;
     433            0 :     case Pid::LINE_WIDTH:
     434            0 :         return LINEWIDTH_DEFAULT;
     435            0 :     case Pid::TPC1:
     436            0 :         return estimateRanges().topTpc;
     437            0 :     case Pid::FBPARENTHESIS1:
     438            0 :         return estimateRanges().bottomTpc;
     439            0 :     case Pid::PITCH:
     440            0 :         return estimateRanges().topPitch;
     441            0 :     case Pid::FBPARENTHESIS2:
     442            0 :         return estimateRanges().bottomPitch;
     443            0 :     case Pid::FBPARENTHESIS3:
     444            0 :         return int(estimateRanges().topPitch / PITCH_DELTA_OCTAVE) - 1;
     445            0 :     case Pid::FBPARENTHESIS4:
     446            0 :         return int(estimateRanges().bottomPitch / PITCH_DELTA_OCTAVE) - 1;
     447           18 :     default:
     448           18 :         return EngravingItem::propertyDefault(id);
     449              :     }
     450              :     //return QVariant();
     451              : }
     452              : 
     453              : //---------------------------------------------------------
     454              : //   nextSegmentElement
     455              : //---------------------------------------------------------
     456              : 
     457            0 : EngravingItem* Ambitus::nextSegmentElement()
     458              : {
     459            0 :     return segment()->firstInNextSegments(staffIdx());
     460              : }
     461              : 
     462              : //---------------------------------------------------------
     463              : //   prevSegmentElement
     464              : //---------------------------------------------------------
     465              : 
     466            0 : EngravingItem* Ambitus::prevSegmentElement()
     467              : {
     468            0 :     return segment()->lastInPrevSegments(staffIdx());
     469              : }
     470              : 
     471              : //---------------------------------------------------------
     472              : //   accessibleInfo
     473              : //---------------------------------------------------------
     474              : 
     475            0 : String Ambitus::accessibleInfo() const
     476              : {
     477            0 :     if (!tpcIsValid(m_topTpc) || !tpcIsValid(m_bottomTpc)) {
     478            0 :         return EngravingItem::accessibleInfo();
     479              :     }
     480            0 :     return EngravingItem::accessibleInfo() + u"; "
     481            0 :            + muse::mtrc("engraving", "Top pitch: %1; Bottom pitch: %2")
     482            0 :            .arg(tpc2name(topTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false) + String::number(topOctave()),
     483            0 :                 tpc2name(bottomTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false) + String::number(bottomOctave()));
     484              : }
     485              : 
     486            4 : AccidentalType Ambitus::accidentalType(int tpc, Key key)
     487              : {
     488            4 :     AccidentalType accidType = AccidentalType::NONE;
     489              :     // if (13 <= (tpc - key) <= 19) there is no accidental)
     490            4 :     if (tpc - int(key) >= 13 && tpc - int(key) <= 19) {
     491            3 :         accidType = AccidentalType::NONE;
     492              :     } else {
     493            1 :         AccidentalVal accidVal = tpc2alter(tpc);
     494            1 :         accidType = Accidental::value2subtype(accidVal);
     495            1 :         if (accidType == AccidentalType::NONE) {
     496            0 :             accidType = AccidentalType::NATURAL;
     497              :         }
     498              :     }
     499              : 
     500            4 :     return accidType;
     501              : }
     502              : 
     503              : //! TODO Seems to need to be moved to utilities
     504            4 : int Ambitus::staffLine(int tpc, int pitch, ClefType clf)
     505              : {
     506            4 :     int line = absStep(tpc, pitch);
     507            4 :     line = relStep(line, clf);
     508            4 :     return line;
     509              : }
        

Generated by: LCOV version 2.0-1