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 "converterutils.h"
23 :
24 : #include <QJsonDocument>
25 : #include <QJsonObject>
26 :
27 : #include "async/notifylist.h"
28 :
29 : #include "notation/inotationinteraction.h"
30 : #include "notation/inotationparts.h"
31 :
32 : #include "convertercodes.h"
33 :
34 : using namespace muse;
35 : using namespace mu::converter;
36 : using namespace mu::notation;
37 :
38 0 : RetVal<TransposeOptions> ConverterUtils::parseTransposeOptions(const std::string& optionsJson)
39 : {
40 0 : QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(optionsJson));
41 0 : if (!doc.isObject()) {
42 0 : LOGW() << "Transpose options JSON is not an object: " << optionsJson;
43 0 : return make_ret(Err::InvalidTransposeOptions);
44 : }
45 :
46 0 : QJsonObject optionsObj = doc.object();
47 :
48 0 : return parseTransposeOptions(optionsObj);
49 0 : }
50 :
51 0 : RetVal<TransposeOptions> ConverterUtils::parseTransposeOptions(const QJsonObject& optionsObj)
52 : {
53 0 : TransposeOptions options;
54 :
55 0 : const QString modeName = optionsObj["mode"].toString();
56 0 : if (modeName == "by_key" || modeName == "to_key") { // "by_key" for backwards compatibility
57 0 : options.mode = TransposeMode::TO_KEY;
58 0 : } else if (modeName == "by_interval") {
59 0 : options.mode = TransposeMode::BY_INTERVAL;
60 0 : } else if (modeName == "diatonically") {
61 0 : options.mode = TransposeMode::DIATONICALLY;
62 : } else {
63 0 : LOGW() << "Transpose: invalid \"mode\" option: " << modeName;
64 0 : return make_ret(Err::InvalidTransposeOptions);
65 : }
66 :
67 0 : const QString directionName = optionsObj["direction"].toString();
68 0 : if (directionName == "up") {
69 0 : options.direction = TransposeDirection::UP;
70 0 : } else if (directionName == "down") {
71 0 : options.direction = TransposeDirection::DOWN;
72 0 : } else if (directionName == "closest") {
73 0 : options.direction = TransposeDirection::CLOSEST;
74 : } else {
75 0 : LOGW() << "Transpose: invalid \"direction\" option: " << directionName;
76 0 : return make_ret(Err::InvalidTransposeOptions);
77 : }
78 :
79 0 : constexpr int defaultKey = int(Key::INVALID);
80 0 : const Key targetKey = Key(optionsObj["targetKey"].toInt(defaultKey));
81 0 : if (options.mode == TransposeMode::TO_KEY) {
82 0 : const bool targetKeyValid = int(Key::MIN) <= int(targetKey) && int(targetKey) <= int(Key::MAX);
83 0 : if (!targetKeyValid) {
84 0 : LOGW() << "Transpose: invalid targetKey: " << int(targetKey);
85 0 : return make_ret(Err::InvalidTransposeOptions);
86 : }
87 : }
88 :
89 0 : const int transposeInterval = optionsObj["transposeInterval"].toInt(-1);
90 0 : constexpr int INTERVAL_LIST_SIZE = 26;
91 :
92 0 : if (options.mode != TransposeMode::TO_KEY) {
93 0 : const bool transposeIntervalValid = -1 < transposeInterval && transposeInterval < INTERVAL_LIST_SIZE;
94 0 : if (!transposeIntervalValid) {
95 0 : LOGW() << "Transpose: invalid transposeInterval: " << transposeInterval;
96 0 : return make_ret(Err::InvalidTransposeOptions);
97 : }
98 : }
99 :
100 0 : options.interval = transposeInterval;
101 0 : options.key = targetKey;
102 0 : options.needTransposeKeys = optionsObj["transposeKeySignatures"].toBool();
103 0 : options.needTransposeChordNames = optionsObj["transposeChordNames"].toBool();
104 0 : options.needTransposeDoubleSharpsFlats = optionsObj["useDoubleSharpsFlats"].toBool();
105 :
106 0 : RetVal<TransposeOptions> result;
107 0 : result.ret = make_ret(Ret::Code::Ok);
108 0 : result.val = options;
109 :
110 0 : return result;
111 0 : }
112 :
113 0 : muse::RetVal<ConvertRegion> ConverterUtils::parseRegion(const std::string& regionJson)
114 : {
115 0 : QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(regionJson));
116 0 : if (!doc.isObject()) {
117 0 : LOGE() << "Invalid Region JSON: it is not an object: " << regionJson;
118 0 : return make_ret(Err::InvalidScoreRegion);
119 : }
120 :
121 0 : auto parsePosition = [](const QJsonObject& o, ConvertRegion::Position& p) {
122 0 : if (o.contains("measure") && o["measure"].isDouble()) {
123 0 : p.measureIdx = static_cast<size_t>(o["measure"].toInt() - 1);
124 : }
125 0 : if (o.contains("staff") && o["staff"].isDouble()) {
126 0 : p.staffIdx = static_cast<size_t>(o["staff"].toInt() - 1);
127 : }
128 0 : };
129 :
130 0 : QJsonObject obj = doc.object();
131 0 : ConvertRegion region;
132 :
133 0 : if (obj.contains("start") && obj["start"].isObject()) {
134 0 : parsePosition(obj["start"].toObject(), region.start);
135 : }
136 :
137 0 : if (obj.contains("end") && obj["end"].isObject()) {
138 0 : parsePosition(obj["end"].toObject(), region.end);
139 : }
140 :
141 0 : if (obj.contains("voices") && obj["voices"].isArray()) {
142 0 : QJsonArray arr = obj["voices"].toArray();
143 0 : region.voiceIdxSet.reserve(arr.size());
144 :
145 0 : for (const auto& val : arr) {
146 0 : if (val.isDouble()) {
147 0 : region.voiceIdxSet.insert(val.toInt() - 1);
148 : }
149 : }
150 0 : }
151 :
152 0 : if (region.start.staffIdx != muse::nidx) {
153 0 : IF_ASSERT_FAILED(region.start.staffIdx <= region.end.staffIdx) {
154 0 : return make_ret(Err::InvalidScoreRegion);
155 : }
156 : }
157 :
158 0 : if (region.start.measureIdx != muse::nidx) {
159 0 : IF_ASSERT_FAILED(region.start.measureIdx <= region.end.measureIdx) {
160 0 : return make_ret(Err::InvalidScoreRegion);
161 : }
162 : }
163 :
164 0 : return RetVal<ConvertRegion>::make_ok(region);
165 0 : }
166 :
167 0 : Ret ConverterUtils::applyTranspose(const INotationPtr notation, const std::string& optionsJson)
168 : {
169 0 : RetVal<TransposeOptions> options = parseTransposeOptions(optionsJson);
170 0 : if (!options.ret) {
171 0 : return options.ret;
172 : }
173 :
174 0 : return applyTranspose(notation, options.val);
175 0 : }
176 :
177 0 : Ret ConverterUtils::applyTranspose(const INotationPtr notation, const TransposeOptions& options)
178 : {
179 0 : INotationInteractionPtr interaction = notation ? notation->interaction() : nullptr;
180 0 : if (!interaction) {
181 0 : return make_ret(Ret::Code::InternalError);
182 : }
183 :
184 0 : interaction->select(SelectionTarget::All);
185 :
186 0 : bool ok = interaction->transpose(options);
187 :
188 0 : return ok ? make_ret(Ret::Code::Ok) : make_ret(Err::TransposeFailed);
189 0 : }
190 :
191 0 : void ConverterUtils::setVisibleParts(const INotationPtr notation, const std::vector<size_t>& visibleParts)
192 : {
193 : // Receiving in a 0 based order
194 0 : const INotationPartsPtr parts = notation->parts();
195 0 : const muse::async::NotifyList<const Part*> partList = parts->partList();
196 0 : std::vector<muse::ID> visiblePartIds;
197 :
198 0 : for (size_t i = 0; i < partList.size(); ++i) {
199 0 : if (muse::contains(visibleParts, i)) {
200 0 : const Part* p = partList.at(i);
201 0 : visiblePartIds.push_back(p->id());
202 : }
203 : }
204 :
205 0 : for (const Part* p : partList) {
206 0 : parts->setPartVisible(p->id(), muse::contains(visiblePartIds, p->id()));
207 : }
208 0 : }
|