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