LCOV - code coverage report
Current view: top level - muse/framework/mpe/internal - articulationprofilesrepository.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 150 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-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 "articulationprofilesrepository.h"
      24              : 
      25              : #include <QJsonDocument>
      26              : #include <QJsonObject>
      27              : #include <QJsonArray>
      28              : #include <QString>
      29              : 
      30              : #include "log.h"
      31              : 
      32              : #include "internal/articulationstringutils.h"
      33              : 
      34              : using namespace muse;
      35              : using namespace muse::mpe;
      36              : using namespace muse::async;
      37              : 
      38              : static const std::map<ArticulationFamily, io::path_t> DEFAULT_ARTICULATION_PROFILES =
      39              : {
      40              :     { ArticulationFamily::Keyboards, io::path_t(":/mpe/resources/general_keyboard_articulations_profile.json") },
      41              :     { ArticulationFamily::Strings, io::path_t(":/mpe/resources/general_strings_articulations_profile.json") },
      42              :     { ArticulationFamily::Winds, io::path_t(":/mpe/resources/general_winds_articulations_profile.json") },
      43              :     { ArticulationFamily::Percussions, io::path_t(":/mpe/resources/general_percussion_articulations_profile.json") },
      44              :     { ArticulationFamily::Voices, io::path_t(":/mpe/resources/general_voice_articulations_profile.json") }
      45              : };
      46              : 
      47              : static const QString SUPPORTED_FAMILIES = "supportedFamilies";
      48              : static const QString PATTERNS_KEY = "patterns";
      49              : static const QString PATTERN_POS_KEY = "patternPosition";
      50              : static const QString OFFSET_POS_KEY = "offsetPosition";
      51              : static const QString OFFSET_VAL_KEY = "offsetValue";
      52              : 
      53              : // Arrangement
      54              : static const QString ARRANGEMENT_PATTERN_KEY = "arrangementPattern";
      55              : static const QString DURATION_FACTOR_KEY = "durationFactor";
      56              : static const QString TIMESTAMP_OFFSET_KEY = "timestampOffset";
      57              : 
      58              : // Pitch
      59              : static const QString PITCH_PATTERN_KEY = "pitchPattern";
      60              : static const QString PITCH_OFFSETS_KEY = "pitchOffsets";
      61              : 
      62              : // Expression
      63              : static const QString EXPRESSION_PATTERN = "expressionPattern";
      64              : static const QString MAX_AMPLITUDE_LEVEL_KEY = "maxAmplitudeLevel";
      65              : static const QString AMPLITUDE_TIME_SHIFT = "amplitudeTimeShift";
      66              : static const QString DYNAMIC_OFFSETS_KEY = "dynamicOffsets";
      67              : 
      68            0 : ArticulationsProfilePtr ArticulationProfilesRepository::createNew() const
      69              : {
      70            0 :     return std::make_shared<ArticulationsProfile>();
      71              : }
      72              : 
      73            0 : ArticulationsProfilePtr ArticulationProfilesRepository::defaultProfile(const ArticulationFamily family) const
      74              : {
      75            0 :     auto search = m_defaultProfiles.find(family);
      76              : 
      77            0 :     if (search != m_defaultProfiles.cend()) {
      78            0 :         return search->second;
      79              :     }
      80              : 
      81            0 :     auto pathSearch = DEFAULT_ARTICULATION_PROFILES.find(family);
      82              : 
      83            0 :     if (pathSearch == DEFAULT_ARTICULATION_PROFILES.cend()) {
      84            0 :         LOGE() << "Unable to find path for undefined articulations family";
      85            0 :         return nullptr;
      86              :     }
      87              : 
      88            0 :     ArticulationsProfilePtr result = loadProfile(pathSearch->second);
      89            0 :     m_defaultProfiles.emplace(family, result);
      90              : 
      91            0 :     return result;
      92            0 : }
      93              : 
      94            0 : ArticulationsProfilePtr ArticulationProfilesRepository::loadProfile(const io::path_t& path) const
      95              : {
      96            0 :     RetVal<ByteArray> fileReading = fileSystem()->readFile(path);
      97              : 
      98            0 :     if (!fileReading.ret) {
      99            0 :         LOGE() << "Unable to read profile, path: " << path;
     100            0 :         return nullptr;
     101              :     }
     102              : 
     103            0 :     QJsonParseError err;
     104              : 
     105            0 :     QJsonDocument file = QJsonDocument::fromJson(fileReading.val.toQByteArrayNoCopy(), &err);
     106            0 :     if (err.error != QJsonParseError::NoError) {
     107            0 :         LOGE() << err.errorString();
     108            0 :         return nullptr;
     109              :     }
     110              : 
     111            0 :     ArticulationsProfilePtr result = std::make_shared<ArticulationsProfile>();
     112              : 
     113            0 :     QJsonObject rootObj = file.object();
     114              : 
     115            0 :     result->supportedFamilies = supportedFamiliesFromJson(rootObj.value(SUPPORTED_FAMILIES).toArray());
     116              : 
     117            0 :     QJsonObject articulationPatterns = rootObj.value(PATTERNS_KEY).toObject();
     118              : 
     119            0 :     for (const QString& key : articulationPatterns.keys()) {
     120            0 :         result->setPattern(articulationTypeFromString(key),
     121            0 :                            patternsScopeFromJson(articulationPatterns.value(key).toArray()));
     122            0 :     }
     123              : 
     124            0 :     return result;
     125            0 : }
     126              : 
     127            0 : void ArticulationProfilesRepository::saveProfile(const io::path_t& path, const ArticulationsProfilePtr profilePtr)
     128              : {
     129            0 :     IF_ASSERT_FAILED(profilePtr) {
     130            0 :         return;
     131              :     }
     132              : 
     133            0 :     QJsonObject rootObj;
     134              : 
     135            0 :     rootObj.insert(SUPPORTED_FAMILIES, supportedFamiliesToJson(profilePtr->supportedFamilies));
     136              : 
     137            0 :     QJsonObject articulationPatterns;
     138              : 
     139            0 :     for (const auto& pair : profilePtr->data()) {
     140            0 :         articulationPatterns.insert(articulationTypeToString(pair.first),
     141            0 :                                     patternsScopeToJson(pair.second));
     142              :     }
     143              : 
     144            0 :     rootObj.insert(PATTERNS_KEY, articulationPatterns);
     145              : 
     146            0 :     QByteArray json = QJsonDocument(rootObj).toJson();
     147            0 :     Ret fileWriting = fileSystem()->writeFile(path, ByteArray::fromQByteArrayNoCopy(json));
     148              : 
     149            0 :     if (!fileWriting) {
     150            0 :         LOGE() << "Unable to write MPE Articulation Profile, err: " << fileWriting.toString();
     151            0 :         return;
     152              :     }
     153              : 
     154            0 :     m_profileChanged.send(path);
     155            0 : }
     156              : 
     157            0 : Channel<io::path_t> ArticulationProfilesRepository::profileChanged() const
     158              : {
     159            0 :     return m_profileChanged;
     160              : }
     161              : 
     162            0 : std::vector<ArticulationFamily> ArticulationProfilesRepository::supportedFamiliesFromJson(const QJsonArray& array) const
     163              : {
     164            0 :     std::vector<ArticulationFamily> result;
     165            0 :     result.reserve(array.size());
     166              : 
     167            0 :     for (const QJsonValue& val : array) {
     168            0 :         result.push_back(articulationFamilyFromString(val.toString()));
     169            0 :     }
     170              : 
     171            0 :     return result;
     172            0 : }
     173              : 
     174            0 : QJsonArray ArticulationProfilesRepository::supportedFamiliesToJson(const std::vector<ArticulationFamily>& families) const
     175              : {
     176            0 :     QJsonArray result;
     177              : 
     178            0 :     for (const auto& family : families) {
     179            0 :         result.append(articulationFamilyToString(family));
     180              :     }
     181              : 
     182            0 :     return result;
     183            0 : }
     184              : 
     185            0 : ArticulationPattern ArticulationProfilesRepository::patternsScopeFromJson(const QJsonArray& array) const
     186              : {
     187            0 :     ArticulationPattern result;
     188              : 
     189            0 :     for (const QJsonValue& val : array) {
     190            0 :         QJsonObject patternObj = val.toObject();
     191              : 
     192            0 :         duration_percentage_t position = patternObj.value(PATTERN_POS_KEY).toInt();
     193              : 
     194            0 :         ArrangementPattern arrangementPattern = arrangementPatternFromJson(patternObj.value(ARRANGEMENT_PATTERN_KEY).toObject());
     195            0 :         PitchPattern pitchPattern = pitchPatternFromJson(patternObj.value(PITCH_PATTERN_KEY).toObject());
     196            0 :         ExpressionPattern expressionPattern = expressionPatternFromJson(patternObj.value(EXPRESSION_PATTERN).toObject());
     197              : 
     198            0 :         ArticulationPatternSegment articulation;
     199            0 :         articulation.arrangementPattern = std::move(arrangementPattern);
     200            0 :         articulation.pitchPattern = std::move(pitchPattern);
     201            0 :         articulation.expressionPattern = std::move(expressionPattern);
     202              : 
     203            0 :         result.emplace(position, std::move(articulation));
     204            0 :     }
     205              : 
     206            0 :     return result;
     207            0 : }
     208              : 
     209            0 : QJsonArray ArticulationProfilesRepository::patternsScopeToJson(const ArticulationPattern& scope) const
     210              : {
     211            0 :     QJsonArray result;
     212              : 
     213            0 :     for (const auto& pair : scope) {
     214            0 :         QJsonObject pattern;
     215            0 :         pattern.insert(PATTERN_POS_KEY, static_cast<int>(pair.first));
     216            0 :         pattern.insert(ARRANGEMENT_PATTERN_KEY, arrangementPatternToJson(pair.second.arrangementPattern));
     217            0 :         pattern.insert(PITCH_PATTERN_KEY, pitchPatternToJson(pair.second.pitchPattern));
     218            0 :         pattern.insert(EXPRESSION_PATTERN, expressionPatternToJson(pair.second.expressionPattern));
     219              : 
     220            0 :         result.append(pattern);
     221            0 :     }
     222              : 
     223            0 :     return result;
     224            0 : }
     225              : 
     226            0 : ArrangementPattern ArticulationProfilesRepository::arrangementPatternFromJson(const QJsonObject& obj) const
     227              : {
     228            0 :     ArrangementPattern result;
     229              : 
     230            0 :     result.durationFactor = obj.value(DURATION_FACTOR_KEY).toInt();
     231            0 :     result.timestampOffset = obj.value(TIMESTAMP_OFFSET_KEY).toInt();
     232              : 
     233            0 :     return result;
     234              : }
     235              : 
     236            0 : QJsonObject ArticulationProfilesRepository::arrangementPatternToJson(const ArrangementPattern& pattern) const
     237              : {
     238            0 :     QJsonObject result;
     239              : 
     240            0 :     result.insert(DURATION_FACTOR_KEY, static_cast<int>(pattern.durationFactor));
     241            0 :     result.insert(TIMESTAMP_OFFSET_KEY, static_cast<int>(pattern.timestampOffset));
     242              : 
     243            0 :     return result;
     244            0 : }
     245              : 
     246            0 : PitchPattern ArticulationProfilesRepository::pitchPatternFromJson(const QJsonObject& obj) const
     247              : {
     248            0 :     PitchPattern result;
     249              : 
     250            0 :     QJsonArray offsets = obj.value(PITCH_OFFSETS_KEY).toArray();
     251              : 
     252            0 :     for (const QJsonValue pitchOffset : offsets) {
     253            0 :         QJsonObject offsetObj = pitchOffset.toObject();
     254              : 
     255            0 :         result.pitchOffsetMap.emplace(offsetObj.value(OFFSET_POS_KEY).toInt(),
     256            0 :                                       offsetObj.value(OFFSET_VAL_KEY).toInt());
     257            0 :     }
     258              : 
     259            0 :     return result;
     260            0 : }
     261              : 
     262            0 : QJsonObject ArticulationProfilesRepository::pitchPatternToJson(const PitchPattern& pattern) const
     263              : {
     264            0 :     QJsonObject result;
     265              : 
     266            0 :     QJsonArray pitchOffsets;
     267              : 
     268            0 :     for (const auto& pair : pattern.pitchOffsetMap) {
     269            0 :         QJsonObject offsetObj;
     270            0 :         offsetObj.insert(OFFSET_POS_KEY, static_cast<int>(pair.first));
     271            0 :         offsetObj.insert(OFFSET_VAL_KEY, static_cast<int>(pair.second));
     272              : 
     273            0 :         pitchOffsets.append(std::move(offsetObj));
     274            0 :     }
     275              : 
     276            0 :     result.insert(PITCH_OFFSETS_KEY, pitchOffsets);
     277              : 
     278            0 :     return result;
     279            0 : }
     280              : 
     281            0 : ExpressionPattern ArticulationProfilesRepository::expressionPatternFromJson(const QJsonObject& obj) const
     282              : {
     283            0 :     ExpressionPattern result;
     284              : 
     285            0 :     QJsonArray offsets = obj.value(DYNAMIC_OFFSETS_KEY).toArray();
     286              : 
     287            0 :     for (const QJsonValue offset : offsets) {
     288            0 :         QJsonObject offsetObj = offset.toObject();
     289            0 :         result.dynamicOffsetMap.emplace(offsetObj.value(OFFSET_POS_KEY).toInt(),
     290            0 :                                         offsetObj.value(OFFSET_VAL_KEY).toInt());
     291            0 :     }
     292              : 
     293            0 :     return result;
     294            0 : }
     295              : 
     296            0 : QJsonObject ArticulationProfilesRepository::expressionPatternToJson(const ExpressionPattern& pattern) const
     297              : {
     298            0 :     QJsonObject result;
     299              : 
     300            0 :     QJsonArray dynamicOffsets;
     301              : 
     302            0 :     for (const auto& pair : pattern.dynamicOffsetMap) {
     303            0 :         QJsonObject offsetObj;
     304            0 :         offsetObj.insert(OFFSET_POS_KEY, static_cast<int>(pair.first));
     305            0 :         offsetObj.insert(OFFSET_VAL_KEY, static_cast<int>(pair.second));
     306              : 
     307            0 :         dynamicOffsets.append(std::move(offsetObj));
     308            0 :     }
     309              : 
     310            0 :     result.insert(DYNAMIC_OFFSETS_KEY, dynamicOffsets);
     311              : 
     312            0 :     return result;
     313            0 : }
        

Generated by: LCOV version 2.0-1