LCOV - code coverage report
Current view: top level - src/converter/internal/compat - scoreelementsscanner.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.8 % 128 115
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) 2025 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 "scoreelementsscanner.h"
      24              : 
      25              : #include "engraving/types/typesconv.h"
      26              : #include "engraving/dom/score.h"
      27              : #include "engraving/dom/part.h"
      28              : #include "engraving/dom/note.h"
      29              : #include "engraving/dom/rest.h"
      30              : #include "engraving/dom/harmony.h"
      31              : #include "engraving/dom/tempotext.h"
      32              : #include "engraving/dom/dynamic.h"
      33              : 
      34              : using namespace mu::converter;
      35              : using namespace mu::engraving;
      36              : 
      37              : struct ScannerData {
      38              :     // out
      39              :     ElementMap elements;
      40              :     std::set<Chord*> chords;
      41              :     std::set<Spanner*> spanners;
      42              : };
      43              : 
      44           73 : static bool itemAccepted(const EngravingItem* item)
      45              : {
      46              :     // Ignore temporary / invalid elements and elements that cannot be interacted with
      47           73 :     if (!item || !item->part() || !item->selectable() || !item->isInteractionAvailable()) {
      48            9 :         return false;
      49              :     }
      50              : 
      51              :     static const ElementTypeSet NOTE_PARTS {
      52              :         ElementType::ACCIDENTAL,
      53              :         ElementType::STEM,
      54              :         ElementType::HOOK,
      55              :         ElementType::BEAM,
      56              :         ElementType::NOTEDOT,
      57              :         ElementType::TIE,
      58              :         ElementType::TIE_SEGMENT,
      59           66 :     };
      60              : 
      61           64 :     if (muse::contains(NOTE_PARTS, item->type())) {
      62           18 :         return false;
      63              :     }
      64              : 
      65           46 :     if (item->isBarLine() && item->tick().ticks() == 0) {
      66            0 :         return false;
      67              :     }
      68              : 
      69           46 :     return true;
      70              : }
      71              : 
      72           18 : static bool isChordArticulation(const EngravingItem* item)
      73              : {
      74           18 :     const EngravingObject* parent = item->ownershipParent();
      75           18 :     if (!parent || !parent->isChord()) {
      76           14 :         return false;
      77              :     }
      78              : 
      79              :     static const ElementTypeSet CHORD_ARTICULATION_TYPES {
      80              :         ElementType::ARPEGGIO,
      81              :         ElementType::TREMOLO_SINGLECHORD,
      82              :         ElementType::ORNAMENT,
      83            6 :     };
      84              : 
      85            4 :     return muse::contains(CHORD_ARTICULATION_TYPES, item->type());
      86              : }
      87              : 
      88           28 : static muse::String noteName(const Note* note)
      89              : {
      90           28 :     return tpc2name(note->tpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO) + String::number(note->octave());
      91              : }
      92              : 
      93            5 : static ElementInfo::NoteList chordToNotes(const Chord* chord)
      94              : {
      95            5 :     ElementInfo::NoteList notes;
      96            5 :     notes.reserve(chord->notes().size());
      97              : 
      98           20 :     for (const Note* note : chord->notes()) {
      99           15 :         ElementInfo::Note info;
     100           15 :         info.name = noteName(note);
     101              : 
     102           15 :         if (note->tieFor() && note->tieFor()->endNote()) {
     103            0 :             info.data[u"tied"] = muse::Val(true);
     104              :         }
     105              : 
     106           15 :         notes.emplace_back(std::move(info));
     107           15 :     }
     108              : 
     109            5 :     return notes;
     110            0 : }
     111              : 
     112           20 : static ElementInfo::Duration durationInfo(const TDuration& dur)
     113              : {
     114           20 :     ElementInfo::Duration result;
     115           20 :     result.name = TConv::translatedUserName(dur.type());
     116           20 :     result.dots = dur.dots();
     117              : 
     118           20 :     return result;
     119            0 : }
     120              : 
     121           29 : static bool hasBeat(ElementType type)
     122              : {
     123              :     static const ElementTypeSet NO_BEAT {
     124              :         ElementType::BAR_LINE,
     125           31 :     };
     126              : 
     127           29 :     return !muse::contains(NO_BEAT, type);
     128              : }
     129              : 
     130           73 : static void addElementInfoIfNeed(ScannerData* scannerData, EngravingItem* item)
     131              : {
     132           73 :     if (!itemAccepted(item)) {
     133           41 :         return;
     134              :     }
     135              : 
     136           46 :     ElementType type = item->type();
     137           46 :     ElementInfo info;
     138           46 :     bool locationIsSet = false;
     139              : 
     140           46 :     if (item->isNote()) {
     141           28 :         Note* note = toNote(item);
     142           28 :         Chord* chord = note->chord();
     143           28 :         if (chord->notes().size() > 1) {
     144           15 :             if (muse::contains(scannerData->chords, chord)) {
     145           14 :                 return;
     146              :             }
     147              : 
     148            1 :             type = chord->type();
     149            1 :             scannerData->chords.insert(chord);
     150            1 :             info.notes = chordToNotes(chord);
     151              :         } else {
     152           13 :             info.name = noteName(note);
     153              : 
     154           13 :             if (note->tieFor() && note->tieFor()->endNote()) {
     155            0 :                 info.data[u"tied"] = muse::Val(true);
     156              :             }
     157              :         }
     158           14 :         info.duration = durationInfo(chord->durationType());
     159           18 :     } else if (isChordArticulation(item)) {
     160            4 :         Chord* chord = toChord(item->ownershipParent());
     161            4 :         scannerData->chords.insert(chord);
     162            4 :         info.name = item->translatedSubtypeUserName();
     163            4 :         info.notes = chordToNotes(chord);
     164            4 :         info.duration = durationInfo(chord->durationType());
     165           14 :     } else if (item->isRest()) {
     166            2 :         info.duration = durationInfo(toRest(item)->durationType());
     167           12 :     } else if (item->isSpannerSegment()) {
     168            3 :         Spanner* spanner = toSpannerSegment(item)->spanner();
     169            3 :         if (muse::contains(scannerData->spanners, spanner)) {
     170            0 :             return;
     171              :         }
     172              : 
     173            3 :         item = spanner;
     174            3 :         type = spanner->type();
     175            3 :         info.name = spanner->translatedSubtypeUserName();
     176            3 :         scannerData->spanners.insert(spanner);
     177              : 
     178            3 :         const Segment* startSegment = spanner->startSegment();
     179            3 :         if (startSegment) {
     180            3 :             const EngravingItem::BarBeat barbeat = startSegment->barbeat();
     181            3 :             info.start.measureIdx = barbeat.bar - 1;
     182            3 :             info.start.beat = barbeat.beat - 1.;
     183            3 :             info.start.staffIdx = track2staff(spanner->track());
     184            3 :             info.start.voiceIdx = track2voice(spanner->track());
     185              :         }
     186              : 
     187            3 :         const Segment* endSegment = spanner->endSegment();
     188            3 :         if (endSegment) {
     189            3 :             const EngravingItem::BarBeat barbeat = endSegment->barbeat();
     190            3 :             info.end.measureIdx = barbeat.bar - 1;
     191            3 :             info.end.beat = barbeat.beat - 1.;
     192            3 :             info.end.staffIdx = track2staff(spanner->track2());
     193            3 :             info.end.voiceIdx = track2voice(spanner->track2());
     194              :         }
     195              : 
     196            3 :         locationIsSet = startSegment || endSegment;
     197            9 :     } else if (item->isHarmony()) {
     198            0 :         info.name = toHarmony(item)->harmonyName();
     199            9 :     } else if (item->isTempoText()) {
     200            0 :         const TempoText* tempo = toTempoText(item);
     201            0 :         info.data[u"bpm"] = muse::Val(tempo->tempoBpm());
     202            0 :         info.duration = durationInfo(tempo->duration());
     203            9 :     } else if (item->isPlayTechAnnotation()) {
     204            1 :         info.name = item->translatedSubtypeUserName();
     205            8 :     } else if (item->isDynamic()) {
     206            0 :         info.name = TConv::toXml(toDynamic(item)->dynamicType()).ascii();
     207            8 :     } else if (item->isTextBase()) {
     208            0 :         info.data[u"text"] = muse::Val(toTextBase(item)->plainText().toStdString());
     209              :     } else {
     210            8 :         info.name = item->translatedSubtypeUserName();
     211              :     }
     212              : 
     213           32 :     info.type = type;
     214              : 
     215           32 :     const Part* part = item->part();
     216              :     const InstrumentTrackId trackId {
     217              :         part->id(),
     218            0 :         part->instrumentId(item->tick())
     219           32 :     };
     220              : 
     221           32 :     if (!locationIsSet) {
     222           29 :         const EngravingItem::BarBeat barbeat = item->barbeat();
     223           29 :         info.start.staffIdx = item->staffIdx();
     224           29 :         info.start.voiceIdx = item->voice();
     225           29 :         info.start.measureIdx = barbeat.displayedBar - 1;
     226              : 
     227           29 :         if (hasBeat(type)) {
     228           24 :             info.start.beat = barbeat.beat - 1.;
     229              :         }
     230              : 
     231           29 :         info.end = info.start;
     232              :     }
     233              : 
     234           32 :     scannerData->elements[trackId].emplace_back(std::move(info));
     235           46 : }
     236              : 
     237            1 : ElementMap ScoreElementScanner::scanElements(Score* score)
     238              : {
     239            2 :     TRACEFUNC;
     240              : 
     241            1 :     ScannerData data;
     242           74 :     score->scanElements([&](mu::engraving::EngravingItem* item) { addElementInfoIfNeed(&data, item); });
     243              : 
     244              :     // Sort elements: staff -> measure -> beat -> voice
     245            2 :     for (auto& pair : data.elements) {
     246            1 :         std::stable_sort(pair.second.begin(), pair.second.end(), [](const ElementInfo& a, const ElementInfo& b) {
     247          103 :             const auto& A = a.start;
     248          103 :             const auto& B = b.start;
     249              : 
     250              :             // Map negative beats to a value that always sorts last
     251          103 :             const float beatA = (A.beat < 0.f) ? std::numeric_limits<float>::max() : A.beat;
     252          103 :             const float beatB = (B.beat < 0.f) ? std::numeric_limits<float>::max() : B.beat;
     253              : 
     254          103 :             return std::tie(A.staffIdx, A.measureIdx, beatA, A.voiceIdx) < std::tie(B.staffIdx, B.measureIdx, beatB, B.voiceIdx);
     255              :         });
     256              :     }
     257              : 
     258            2 :     return data.elements;
     259            1 : }
        

Generated by: LCOV version 2.0-1