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 : }
|