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 : #include "notationmeta.h"
23 :
24 : #include <cmath>
25 :
26 : #include <QJsonArray>
27 : #include <QJsonObject>
28 : #include <QJsonDocument>
29 :
30 : #include "audio/common/audioutils.h"
31 : #include "audio/common/audiotypes.h"
32 :
33 : #include "engraving/dom/part.h"
34 : #include "engraving/dom/score.h"
35 : #include "engraving/dom/tempotext.h"
36 : #include "engraving/dom/text.h"
37 :
38 : #include "notation/inotation.h"
39 : #include "notation/inotationelements.h" // IWYU pragma: keep
40 :
41 : #include "project/inotationproject.h"
42 : #include "project/iprojectaudiosettings.h"
43 :
44 : #include "log.h"
45 :
46 : using namespace muse;
47 : using namespace mu::converter;
48 : using namespace mu::engraving;
49 : using namespace mu::project;
50 :
51 0 : static QString boolToString(bool b)
52 : {
53 0 : return b ? "true" : "false";
54 : }
55 :
56 0 : static bool shouldTryRecognizeText(const Text* text)
57 : {
58 0 : const TextStyleType type = text->textStyleType();
59 0 : if (type == TextStyleType::DEFAULT || type == TextStyleType::FRAME) {
60 0 : return true;
61 : }
62 :
63 0 : return (int)(type) >= (int)TextStyleType::USER1 && (int)(type) <= (int)TextStyleType::USER12;
64 : }
65 :
66 0 : static QString recognizeTitle(const mu::engraving::Score* score)
67 : {
68 0 : const MeasureBase* mb = score->first();
69 0 : if (!mb || !mb->isVBox()) {
70 0 : return QString();
71 : }
72 :
73 0 : const Text* titleText = nullptr;
74 0 : double maxFontSize = DBL_MIN;
75 0 : double minY = DBL_MAX;
76 :
77 0 : for (const EngravingItem* item : mb->el()) {
78 0 : if (!item || !item->isText()) {
79 0 : continue;
80 : }
81 :
82 0 : const Text* text = toText(item);
83 0 : if (!shouldTryRecognizeText(text)) {
84 0 : continue;
85 : }
86 :
87 0 : if (text->size() < maxFontSize) {
88 0 : continue;
89 : }
90 :
91 0 : if (RealIsEqual(text->size(), maxFontSize) && text->y() > minY) {
92 0 : continue;
93 : }
94 :
95 0 : titleText = text;
96 0 : maxFontSize = text->size();
97 0 : minY = text->y();
98 : }
99 :
100 0 : return titleText ? titleText->plainText().toQString() : QString();
101 : }
102 :
103 0 : static QString recognizeComposer(const mu::engraving::Score* score)
104 : {
105 0 : const MeasureBase* mb = score->first();
106 0 : if (!mb || !mb->isVBox()) {
107 0 : return QString();
108 : }
109 :
110 0 : const Text* rightmostText = nullptr;
111 0 : double rightmostTextX = mb->ldata()->bbox().center().x();
112 :
113 0 : for (const EngravingItem* item : mb->el()) {
114 0 : if (!item || !item->isText()) {
115 0 : continue;
116 : }
117 :
118 0 : const Text* text = toText(item);
119 0 : if (!shouldTryRecognizeText(text)) {
120 0 : continue;
121 : }
122 :
123 0 : if (text->x() > rightmostTextX) {
124 0 : rightmostText = text;
125 0 : rightmostTextX = text->x();
126 : }
127 : }
128 :
129 0 : return rightmostText ? rightmostText->plainText().toQString() : QString();
130 : }
131 :
132 0 : RetVal<std::string> NotationMeta::metaJson(notation::INotationPtr notation)
133 : {
134 0 : IF_ASSERT_FAILED(notation) {
135 0 : return make_ret(Ret::Code::UnknownError);
136 : }
137 :
138 0 : mu::engraving::Score* score = notation->elements()->msScore();
139 :
140 0 : IF_ASSERT_FAILED(score) {
141 0 : return make_ret(Ret::Code::UnknownError);
142 : }
143 :
144 0 : QJsonObject json;
145 :
146 0 : json["title"] = title(score);
147 0 : json["subtitle"] = subtitle(score);
148 0 : json["composer"] = composer(score);
149 0 : json["poet"] = poet(score);
150 0 : json["mscoreVersion"] = score->mscoreVersion().toQString();
151 0 : json["fileVersion"] = score->mscVersion();
152 0 : json["pages"] = static_cast<int>(score->npages()); // no = operator for size_t
153 0 : json["measures"] = static_cast<int>(score->nmeasures()); // no = operator for size_t
154 0 : json["hasLyrics"] = boolToString(score->hasLyrics());
155 0 : json["hasHarmonies"] = boolToString(score->hasHarmonies());
156 0 : json["keysig"] = score->keysig();
157 0 : json["previousSource"] = score->metaTag(u"source").toQString();
158 0 : json["timesig"] = timesig(score);
159 0 : json["duration"] = score->duration();
160 0 : json["lyrics"] = score->extractLyrics().toQString();
161 :
162 0 : auto _tempo = tempo(score);
163 0 : json["tempo"] = _tempo.first;
164 0 : json["tempoText"] = _tempo.second;
165 :
166 0 : json["parts"] = partsJsonArray(score);
167 0 : json["pageFormat"] = pageFormatJson(score->style());
168 0 : json["textFramesData"] = typeDataJson(score);
169 0 : json["tracks"] = tracksJsonArray(notation);
170 :
171 0 : RetVal<std::string> result;
172 0 : result.ret = make_ret(Ret::Code::Ok);
173 0 : result.val = QJsonDocument(json).toJson().toStdString();
174 :
175 0 : return result;
176 0 : }
177 :
178 0 : QString NotationMeta::title(const mu::engraving::Score* score)
179 : {
180 0 : QString title;
181 0 : const mu::engraving::Text* text = score->getText(mu::engraving::TextStyleType::TITLE);
182 0 : if (text) {
183 0 : title = text->plainText();
184 : }
185 :
186 0 : if (title.isEmpty()) {
187 0 : title = score->metaTag(u"workTitle");
188 : }
189 :
190 0 : if (title.isEmpty()) {
191 0 : title = recognizeTitle(score);
192 : }
193 :
194 0 : if (title.isEmpty()) {
195 0 : title = score->name();
196 : }
197 :
198 0 : return title;
199 0 : }
200 :
201 0 : QString NotationMeta::subtitle(const mu::engraving::Score* score)
202 : {
203 0 : QString subtitle;
204 0 : const mu::engraving::Text* text = score->getText(mu::engraving::TextStyleType::SUBTITLE);
205 0 : if (text) {
206 0 : subtitle = text->plainText();
207 : }
208 :
209 0 : return subtitle;
210 0 : }
211 :
212 0 : QString NotationMeta::composer(const mu::engraving::Score* score)
213 : {
214 0 : QString composer;
215 0 : const mu::engraving::Text* text = score->getText(mu::engraving::TextStyleType::COMPOSER);
216 0 : if (text) {
217 0 : composer = text->plainText();
218 : }
219 :
220 0 : if (composer.isEmpty()) {
221 0 : composer = score->metaTag(u"composer");
222 : }
223 :
224 0 : if (composer.isEmpty()) {
225 0 : composer = recognizeComposer(score);
226 : }
227 :
228 0 : return composer;
229 0 : }
230 :
231 0 : QString NotationMeta::poet(const mu::engraving::Score* score)
232 : {
233 0 : QString poet;
234 0 : const mu::engraving::Text* text = score->getText(mu::engraving::TextStyleType::LYRICIST);
235 0 : if (text) {
236 0 : poet = text->plainText();
237 : }
238 :
239 0 : if (poet.isEmpty()) {
240 0 : poet = score->metaTag(u"lyricist");
241 : }
242 :
243 0 : return poet;
244 0 : }
245 :
246 0 : QString NotationMeta::timesig(const mu::engraving::Score* score)
247 : {
248 0 : size_t staves = score->nstaves();
249 0 : size_t tracks = staves * mu::engraving::VOICES;
250 0 : const mu::engraving::Segment* timeSigSegment = score->firstSegmentMM(mu::engraving::SegmentType::TimeSig);
251 0 : if (!timeSigSegment) {
252 0 : return QString();
253 : }
254 :
255 0 : QString timeSig;
256 0 : const mu::engraving::EngravingItem* element = nullptr;
257 0 : for (size_t track = 0; track < tracks; ++track) {
258 0 : element = timeSigSegment->element(static_cast<int>(track));
259 0 : if (element) {
260 0 : break;
261 : }
262 : }
263 :
264 0 : if (element && element->isTimeSig()) {
265 0 : const mu::engraving::TimeSig* ts = mu::engraving::toTimeSig(element);
266 0 : timeSig = QString("%1/%2").arg(ts->numerator()).arg(ts->denominator());
267 : }
268 :
269 0 : return timeSig;
270 0 : }
271 :
272 0 : std::pair<int, QString> NotationMeta::tempo(const mu::engraving::Score* score)
273 : {
274 0 : int tempo = 0;
275 0 : QString tempoText;
276 0 : for (const mu::engraving::Segment* segment = score->firstSegmentMM(mu::engraving::SegmentType::All); segment;
277 0 : segment = segment->next1MM()) {
278 0 : auto annotations = segment->annotations();
279 0 : for (const mu::engraving::EngravingItem* annotation : annotations) {
280 0 : if (annotation && annotation->isTempoText()) {
281 0 : const mu::engraving::TempoText* tt = toTempoText(annotation);
282 0 : tempo = round(tt->tempo().toBPM().val);
283 0 : tempoText = tt->xmlText();
284 : }
285 : }
286 0 : }
287 :
288 0 : return { tempo, tempoText };
289 0 : }
290 :
291 0 : QJsonArray NotationMeta::partsJsonArray(const mu::engraving::Score* score)
292 : {
293 0 : QJsonArray jsonPartsArray;
294 0 : for (const mu::engraving::Part* part : score->parts()) {
295 0 : QJsonObject jsonPart;
296 0 : jsonPart.insert("id", part->id().toQString());
297 0 : jsonPart.insert("name", part->longName().replace(u"\n", u"").toQString());
298 0 : int midiProgram = part->midiProgram();
299 0 : jsonPart.insert("program", midiProgram);
300 0 : jsonPart.insert("instrumentId", part->instrumentId().toQString());
301 0 : jsonPart.insert("lyricCount", part->lyricCount());
302 0 : jsonPart.insert("harmonyCount", part->harmonyCount());
303 0 : jsonPart.insert("hasPitchedStaff", boolToString(part->hasPitchedStaff()));
304 0 : jsonPart.insert("hasTabStaff", boolToString(part->hasTabStaff()));
305 0 : jsonPart.insert("hasDrumStaff", boolToString(part->hasDrumStaff()));
306 0 : jsonPart.insert("isVisible", boolToString(part->show()));
307 0 : jsonPartsArray.append(jsonPart);
308 0 : }
309 :
310 0 : return jsonPartsArray;
311 0 : }
312 :
313 0 : QJsonObject NotationMeta::pageFormatJson(const mu::engraving::MStyle& style)
314 : {
315 0 : QJsonObject format;
316 0 : format.insert("height", round(style.styleD(mu::engraving::Sid::pageHeight) * mu::engraving::INCH));
317 0 : format.insert("width", round(style.styleD(mu::engraving::Sid::pageWidth) * mu::engraving::INCH));
318 0 : format.insert("twosided", boolToString(style.styleB(mu::engraving::Sid::pageTwosided)));
319 :
320 0 : return format;
321 0 : }
322 :
323 0 : static void findTextByType(TextStyleType textStyleType, QStringList& strings, mu::engraving::EngravingItem* element)
324 : {
325 0 : if (!element->isTextBase()) {
326 0 : return;
327 : }
328 :
329 0 : const mu::engraving::TextBase* text = toTextBase(element);
330 0 : if (text->textStyleType() == textStyleType) {
331 0 : strings.append(text->plainText());
332 : }
333 : }
334 :
335 0 : QJsonObject NotationMeta::typeDataJson(mu::engraving::Score* score)
336 : {
337 0 : QJsonObject typesData;
338 : static const std::vector<std::pair<QString, TextStyleType> > namesTypesList {
339 0 : { "titles", TextStyleType::TITLE },
340 0 : { "subtitles", TextStyleType::SUBTITLE },
341 0 : { "composers", TextStyleType::COMPOSER },
342 0 : { "poets", TextStyleType::LYRICIST }
343 0 : };
344 :
345 0 : for (const auto& nameType : namesTypesList) {
346 0 : QJsonArray typeData;
347 0 : QStringList typeTextStrings;
348 0 : score->scanElements([&](EngravingItem* item) { findTextByType(nameType.second, typeTextStrings, item); });
349 0 : for (const auto& typeStr : std::as_const(typeTextStrings)) {
350 0 : typeData.append(typeStr);
351 : }
352 0 : typesData.insert(nameType.first, typeData);
353 0 : }
354 :
355 0 : return typesData;
356 0 : }
357 :
358 0 : QJsonArray NotationMeta::tracksJsonArray(notation::INotationPtr notation)
359 : {
360 0 : QJsonArray jsonTracksArray;
361 :
362 0 : if (!notation) {
363 0 : return jsonTracksArray;
364 : }
365 :
366 0 : INotationProject* project = notation->project();
367 0 : if (!project) {
368 0 : return jsonTracksArray;
369 : }
370 :
371 0 : IProjectAudioSettingsPtr audioSettings = project->audioSettings();
372 0 : if (!audioSettings) {
373 0 : return jsonTracksArray;
374 : }
375 :
376 0 : const TrackInputParamsMap& allTrackInputParams = audioSettings->allTrackInputParams();
377 0 : std::map<InstrumentTrackId, QJsonObject> sortedJsonTracks;
378 :
379 0 : for (const auto& [trackId, inputParams] : allTrackInputParams) {
380 0 : if (inputParams.resourceMeta.id.empty()) {
381 0 : continue;
382 : }
383 :
384 0 : QJsonObject jsonTrack;
385 0 : jsonTrack.insert("instrumentId", trackId.instrumentId.toQString());
386 0 : jsonTrack.insert("partId", trackId.partId.toQString());
387 0 : jsonTrack.insert("type", audio::audioResourceTypeToString(inputParams.resourceMeta.type).toQString());
388 :
389 0 : audio::AudioSourceType sourceType = audio::sourceTypeFromResourceType(inputParams.resourceMeta.type);
390 0 : if (sourceType != audio::AudioSourceType::Fluid) {
391 0 : if (sourceType == audio::AudioSourceType::MuseSampler) {
392 0 : jsonTrack.insert("vendor", QString::fromStdString(inputParams.resourceMeta.attributeVal(
393 0 : u"museVendorName").toStdString()));
394 : } else {
395 0 : jsonTrack.insert("vendor", QString::fromStdString(inputParams.resourceMeta.vendor));
396 : }
397 : }
398 :
399 0 : String name = audioSourceName(inputParams);
400 0 : jsonTrack.insert("name", name.toQString());
401 :
402 0 : QString soundIdStr = QString::fromStdString(inputParams.resourceMeta.id);
403 0 : if (soundIdStr != name) {
404 0 : jsonTrack.insert("soundId", soundIdStr);
405 : }
406 :
407 0 : String category = audioSourceCategoryName(inputParams);
408 0 : if (category != name) {
409 0 : jsonTrack.insert("category", category.toQString());
410 : }
411 :
412 0 : sortedJsonTracks.insert({ trackId, jsonTrack });
413 0 : }
414 :
415 0 : for (const auto& pair : sortedJsonTracks) {
416 0 : jsonTracksArray.append(pair.second);
417 : }
418 :
419 0 : return jsonTracksArray;
420 0 : }
|