LCOV - code coverage report
Current view: top level - src/converter/internal/compat - notationmeta.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 237 0
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              : #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 : }
        

Generated by: LCOV version 2.0-1