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 :
23 : #include <limits>
24 : #include "instrtemplate.h"
25 :
26 : #include "io/file.h"
27 : #include "containers.h"
28 : #include "translation.h"
29 :
30 : #include "rw/xmlreader.h"
31 : #include "rw/xmlwriter.h"
32 : #include "rw/write/twrite.h"
33 : #include "rw/read400/tread.h"
34 : #include "types/typesconv.h"
35 :
36 : #include "bracket.h"
37 : #include "drumset.h"
38 : #include "scoreorder.h"
39 : #include "stafftype.h"
40 : #include "stringdata.h"
41 : #include "stringutils.h"
42 : #include "utils.h"
43 :
44 : #include "log.h"
45 :
46 : using namespace mu;
47 : using namespace muse::io;
48 : using namespace mu::engraving;
49 :
50 : namespace mu::engraving {
51 : std::vector<const InstrumentGroup*> instrumentGroups;
52 : std::vector<const InstrumentGenre*> instrumentGenres;
53 : std::vector<const InstrumentFamily*> instrumentFamilies;
54 : std::vector<MidiArticulation> midiArticulations; // global articulations
55 : std::vector<ScoreOrder> instrumentOrders;
56 :
57 1036 : InstrumentIndex::InstrumentIndex(int g, int i, const InstrumentTemplate* it)
58 1036 : : groupIndex{g}, instrIndex{i}, instrTemplate{it}
59 : {
60 1036 : templateCount = 0;
61 13468 : for (const InstrumentGroup* ig : instrumentGroups) {
62 12432 : templateCount += ig->instrumentTemplates.size();
63 : }
64 1036 : }
65 :
66 13312 : const InstrumentGenre* searchInstrumentGenre(const String& id)
67 : {
68 80832 : for (const InstrumentGenre* ig : instrumentGenres) {
69 80656 : if (ig->id == id) {
70 13136 : return ig;
71 : }
72 : }
73 176 : return nullptr;
74 : }
75 :
76 12224 : static const InstrumentFamily* searchInstrumentFamily(const String& name)
77 : {
78 742320 : for (const InstrumentFamily* fam : instrumentFamilies) {
79 740400 : if (fam->id == name) {
80 10304 : return fam;
81 : }
82 : }
83 1920 : return nullptr;
84 : }
85 :
86 192 : static InstrumentGroup* searchInstrumentGroup(const String& id)
87 : {
88 1248 : for (const InstrumentGroup* g : instrumentGroups) {
89 1056 : if (g->id == id) {
90 0 : return const_cast<InstrumentGroup*>(g);
91 : }
92 : }
93 192 : return nullptr;
94 : }
95 :
96 160 : static MidiArticulation searchArticulation(const String& name)
97 : {
98 880 : for (MidiArticulation a : midiArticulations) {
99 720 : if (a.name == name) {
100 0 : return a;
101 : }
102 720 : }
103 160 : return MidiArticulation();
104 : }
105 :
106 25936 : static int readStaffIdx(XmlReader& e)
107 : {
108 25936 : int idx = e.intAttribute("staff", 1) - 1;
109 25936 : if (idx >= MAX_STAVES) {
110 0 : idx = MAX_STAVES - 1;
111 : }
112 25936 : if (idx < 0) {
113 0 : idx = 0;
114 : }
115 25936 : return idx;
116 : }
117 :
118 1968 : static TraitType traitTypeFromString(const String& str)
119 : {
120 : static const std::unordered_map<String, TraitType> types {
121 0 : { u"transposition", TraitType::Transposition },
122 0 : { u"tuning", TraitType::Tuning },
123 0 : { u"course", TraitType::Course }
124 2048 : };
125 :
126 1968 : return muse::value(types, str.toLower(), TraitType::Unknown);
127 16 : }
128 :
129 : //---------------------------------------------------------
130 : // read InstrumentGroup
131 : //---------------------------------------------------------
132 :
133 192 : void InstrumentGroup::read(XmlReader& e)
134 : {
135 192 : id = e.attribute("id");
136 192 : name = muse::mtrc("engraving/instruments/group", e.attribute("name"));
137 192 : extended = e.intAttribute("extended", 0);
138 :
139 10688 : while (e.readNextStartElement()) {
140 10496 : const AsciiStringView tag(e.name());
141 10496 : if (tag == "instrument" || tag == "Instrument") {
142 10304 : String sid = e.attribute("id");
143 10304 : InstrumentTemplate* t = const_cast<InstrumentTemplate*>(searchTemplate(sid));
144 10304 : if (t == 0) {
145 10304 : t = new InstrumentTemplate;
146 : // init with global articulation
147 10304 : t->midiArticulations.insert(t->midiArticulations.end(), midiArticulations.begin(), midiArticulations.end());
148 10304 : t->sequenceOrder = static_cast<int>(instrumentTemplates.size());
149 10304 : instrumentTemplates.push_back(t);
150 : }
151 10304 : t->read(e);
152 10496 : } else if (tag == "ref") {
153 0 : const InstrumentTemplate* ttt = searchTemplate(e.readText());
154 0 : if (ttt) {
155 0 : InstrumentTemplate* t = new InstrumentTemplate(*ttt);
156 0 : instrumentTemplates.push_back(t);
157 : } else {
158 0 : LOGD("instrument reference not found <%s>", e.text().toUtf8().data());
159 : }
160 192 : } else if (tag == "name") {
161 192 : name = muse::mtrc("engraving/instruments/group", e.readAsciiText().ascii());
162 0 : } else if (tag == "extended") {
163 0 : extended = e.readInt();
164 : } else {
165 0 : e.unknown();
166 : }
167 : }
168 192 : if (id.isEmpty()) {
169 0 : id = name.toLower().replace(u" ", u"-");
170 : }
171 :
172 10496 : for (const InstrumentTemplate* templ : instrumentTemplates) {
173 10304 : const_cast<InstrumentTemplate*>(templ)->groupId = id;
174 : }
175 192 : }
176 :
177 : //---------------------------------------------------------
178 : // clear InstrumentGroup
179 : //---------------------------------------------------------
180 :
181 0 : void InstrumentGroup::clear()
182 : {
183 0 : muse::DeleteAll(instrumentTemplates);
184 0 : instrumentTemplates.clear();
185 0 : }
186 :
187 : //---------------------------------------------------------
188 : // InstrumentTemplate
189 : //---------------------------------------------------------
190 :
191 51520 : InstrumentTemplate::InstrumentTemplate()
192 : {
193 10304 : staffCount = 1;
194 10304 : minPitchA = MIN_PITCH;
195 10304 : maxPitchA = MAX_PITCH;
196 10304 : minPitchP = MIN_PITCH;
197 10304 : maxPitchP = MAX_PITCH;
198 10304 : staffGroup = StaffGroup::STANDARD;
199 10304 : staffTypePreset = 0;
200 10304 : useDrumset = false;
201 10304 : drumset = 0;
202 10304 : extended = false;
203 10304 : singleNoteDynamics = true;
204 10304 : family = nullptr;
205 :
206 51520 : for (int i = 0; i < MAX_STAVES; ++i) {
207 41216 : clefTypes[i].concertClef = ClefType::G;
208 41216 : clefTypes[i].transposingClef = ClefType::G;
209 41216 : staffLines[i] = 5;
210 41216 : smallStaff[i] = false;
211 41216 : bracket[i] = BracketType::NO_BRACKET;
212 41216 : bracketSpan[i] = 0;
213 : }
214 10304 : transpose.diatonic = 0;
215 10304 : transpose.chromatic = 0;
216 10304 : }
217 :
218 0 : InstrumentTemplate::InstrumentTemplate(const InstrumentTemplate& t)
219 : {
220 0 : init(t);
221 0 : }
222 :
223 : //---------------------------------------------------------
224 : // init
225 : //---------------------------------------------------------
226 :
227 352 : void InstrumentTemplate::init(const InstrumentTemplate& t)
228 : {
229 352 : id = t.id;
230 352 : soundId = t.soundId;
231 352 : trackName = t.trackName;
232 352 : instrumentName = t.instrumentName;
233 352 : description = t.description;
234 352 : musicXmlId = t.musicXmlId;
235 352 : staffCount = t.staffCount;
236 352 : extended = t.extended;
237 352 : minPitchA = t.minPitchA;
238 352 : maxPitchA = t.maxPitchA;
239 352 : minPitchP = t.minPitchP;
240 352 : maxPitchP = t.maxPitchP;
241 352 : transpose = t.transpose;
242 352 : staffGroup = t.staffGroup;
243 352 : staffTypePreset = t.staffTypePreset;
244 352 : useDrumset = t.useDrumset;
245 352 : drumset = t.drumset ? new Drumset(*t.drumset) : nullptr;
246 352 : stringData = t.stringData;
247 352 : midiActions = t.midiActions;
248 352 : genres = t.genres;
249 352 : channel = t.channel;
250 352 : family = t.family;
251 352 : singleNoteDynamics = t.singleNoteDynamics;
252 352 : sequenceOrder = t.sequenceOrder;
253 352 : trait = t.trait;
254 352 : groupId = t.groupId;
255 352 : glissandoStyle = t.glissandoStyle;
256 352 : barlineSpan = t.barlineSpan;
257 :
258 1760 : for (int i = 0; i < MAX_STAVES; ++i) {
259 1408 : clefTypes[i] = t.clefTypes[i];
260 1408 : staffLines[i] = t.staffLines[i];
261 1408 : smallStaff[i] = t.smallStaff[i];
262 1408 : bracket[i] = t.bracket[i];
263 1408 : bracketSpan[i] = t.bracketSpan[i];
264 : }
265 352 : }
266 :
267 0 : InstrumentTemplate::~InstrumentTemplate()
268 : {
269 0 : delete drumset;
270 0 : drumset = nullptr;
271 0 : }
272 :
273 0 : InstrumentTemplate& InstrumentTemplate::operator=(const InstrumentTemplate& templ)
274 : {
275 0 : init(templ);
276 0 : return *this;
277 : }
278 :
279 0 : bool InstrumentTemplate::isValid() const
280 : {
281 0 : return !id.empty();
282 : }
283 :
284 : //---------------------------------------------------------
285 : // write
286 : //---------------------------------------------------------
287 :
288 0 : void InstrumentTemplate::write(XmlWriter& xml) const
289 : {
290 0 : xml.startElement("Instrument", { { "id", id } });
291 :
292 0 : if (!soundId.empty()) {
293 0 : xml.tag("soundId", soundId);
294 : }
295 :
296 0 : write::TWrite::write(instrumentName, xml);
297 :
298 0 : if (!instrumentName.longName().empty()) {
299 0 : xml.tag("trackName", instrumentName.longName());
300 : }
301 0 : xml.tag("description", description);
302 0 : xml.tag("musicXMLid", musicXmlId);
303 0 : if (extended) {
304 0 : xml.tag("extended", extended);
305 : }
306 0 : write::TWrite::write(&stringData, xml);
307 0 : if (staffCount > 1) {
308 0 : xml.tag("staves", static_cast<int>(staffCount));
309 : }
310 0 : for (staff_idx_t i = 0; i < staffCount; ++i) {
311 0 : if (clefTypes[i].concertClef == clefTypes[i].transposingClef) {
312 0 : if (i) {
313 0 : xml.tag("clef", { { "staff", i + 1 } }, TConv::toXml(clefTypes[i].concertClef));
314 : } else {
315 0 : xml.tag("clef", TConv::toXml(clefTypes[i].concertClef));
316 : }
317 : } else {
318 0 : if (i) {
319 0 : xml.tag("concertClef", { { "staff", i + 1 } }, TConv::toXml(clefTypes[i].concertClef));
320 0 : xml.tag("transposingClef", { { "staff", i + 1 } }, TConv::toXml(clefTypes[i].transposingClef));
321 : } else {
322 0 : xml.tag("concertClef", TConv::toXml(clefTypes[i].concertClef));
323 0 : xml.tag("transposingClef", TConv::toXml(clefTypes[i].transposingClef));
324 : }
325 : }
326 0 : if (staffLines[i] != 5) {
327 0 : if (i) {
328 0 : xml.tag("stafflines", { { "staff", i + 1 } }, staffLines[i]);
329 : } else {
330 0 : xml.tag("stafflines", staffLines[i]);
331 : }
332 : }
333 0 : if (smallStaff[i]) {
334 0 : if (i) {
335 0 : xml.tag("smallStaff", { { "staff", i + 1 } }, smallStaff[i]);
336 : } else {
337 0 : xml.tag("smallStaff", smallStaff[i]);
338 : }
339 : }
340 :
341 0 : if (bracket[i] != BracketType::NO_BRACKET) {
342 0 : if (i) {
343 0 : xml.tag("bracket", { { "staff", i + 1 } }, int(bracket[i]));
344 : } else {
345 0 : xml.tag("bracket", int(bracket[i]));
346 : }
347 : }
348 0 : if (bracketSpan[i] != 0) {
349 0 : if (i) {
350 0 : xml.tag("bracketSpan", { { "staff", i + 1 } }, bracketSpan[i]);
351 : } else {
352 0 : xml.tag("bracketSpan", bracketSpan[i]);
353 : }
354 : }
355 0 : if (barlineSpan[i]) {
356 0 : if (i > 0) {
357 0 : xml.tag("barlineSpan", { { "staff", i + 1 } }, int { barlineSpan[i] });
358 : } else {
359 0 : xml.tag("barlineSpan", int { barlineSpan[i] });
360 : }
361 : }
362 : }
363 0 : if (minPitchA != MIN_PITCH || maxPitchA != MAX_PITCH) {
364 0 : xml.tag("aPitchRange", String(u"%1-%2").arg(minPitchA).arg(maxPitchA));
365 : }
366 0 : if (minPitchP != MIN_PITCH || maxPitchP != MAX_PITCH) {
367 0 : xml.tag("pPitchRange", String(u"%1-%2").arg(minPitchP).arg(maxPitchP));
368 : }
369 0 : if (transpose.diatonic) {
370 0 : xml.tag("transposeDiatonic", transpose.diatonic);
371 : }
372 0 : if (transpose.chromatic) {
373 0 : xml.tag("transposeChromatic", transpose.chromatic);
374 : }
375 0 : if (useDrumset) {
376 0 : xml.tag("drumset", int(useDrumset));
377 : }
378 0 : if (drumset) {
379 0 : drumset->save(xml);
380 : }
381 :
382 0 : if (!singleNoteDynamics) { // default is true
383 0 : xml.tag("singleNoteDynamics", singleNoteDynamics);
384 : }
385 :
386 0 : xml.tag("glissandoStyle", TConv::toXml(glissandoStyle));
387 :
388 0 : for (const NamedEventList& a : midiActions) {
389 0 : write::TWrite::write(&a, xml, "MidiAction");
390 : }
391 0 : for (const InstrChannel& a : channel) {
392 0 : write::TWrite::write(&a, xml, nullptr);
393 : }
394 0 : for (const MidiArticulation& ma : midiArticulations) {
395 0 : bool isGlobal = false;
396 0 : for (const MidiArticulation& ga : mu::engraving::midiArticulations) {
397 0 : if (ma == ga) {
398 0 : isGlobal = true;
399 0 : break;
400 : }
401 : }
402 0 : if (!isGlobal) {
403 0 : write::TWrite::write(&ma, xml);
404 : }
405 : }
406 0 : if (family) {
407 0 : xml.tag("family", family->id);
408 : }
409 :
410 0 : xml.endElement();
411 0 : }
412 :
413 : //---------------------------------------------------------
414 : // read
415 : //---------------------------------------------------------
416 :
417 41504 : String translateInstrumentName(const String& instrumentId, const String& nameType, const String& text)
418 : {
419 41504 : String disambiguation = instrumentId + u' ' + nameType;
420 83008 : return muse::mtrc("engraving/instruments", text, disambiguation);
421 41504 : }
422 :
423 10304 : void InstrumentTemplate::read(XmlReader& e)
424 : {
425 10304 : id = e.attribute("id");
426 :
427 159808 : while (e.readNextStartElement()) {
428 149504 : const AsciiStringView tag(e.name());
429 :
430 149504 : if (tag == "soundId") {
431 0 : soundId = e.readText();
432 149504 : } else if (tag == "longName" || tag == "name") { // "name" is obsolete
433 9856 : int pos = e.intAttribute("pos", 0);
434 : UNUSED(pos);
435 9856 : instrumentName.setLongName(translateInstrumentName(id, u"longName", e.readText()));
436 139648 : } else if (tag == "shortName" || tag == "short-name") { // "short-name" is obsolete
437 9520 : int pos = e.intAttribute("pos", 0);
438 : UNUSED(pos);
439 9520 : instrumentName.setShortName(translateInstrumentName(id, u"shortName", e.readText()));
440 130128 : } else if (tag == "trackName") {
441 9856 : trackName = translateInstrumentName(id, u"trackName", e.readText());
442 120272 : } else if (tag == "description") {
443 10304 : description = translateInstrumentName(id, u"description", e.readText());
444 109968 : } else if (tag == "extended") {
445 0 : extended = e.readInt();
446 109968 : } else if (tag == "staves") {
447 1104 : staffCount = e.readInt();
448 1104 : bracketSpan[0] = static_cast<int>(staffCount);
449 : // for (int i = 0; i < staves-1; ++i)
450 : // barlineSpan[i] = true;
451 108864 : } else if (tag == "clef") { // sets both transposing and concert clef
452 10000 : int idx = readStaffIdx(e);
453 10000 : ClefType ct = TConv::fromXml(e.readAsciiText(), ClefType::G);
454 10000 : clefTypes[idx].concertClef = ct;
455 10000 : clefTypes[idx].transposingClef = ct;
456 98864 : } else if (tag == "concertClef") {
457 1072 : int idx = readStaffIdx(e);
458 1072 : clefTypes[idx].concertClef = TConv::fromXml(e.readAsciiText(), ClefType::G);
459 97792 : } else if (tag == "transposingClef") {
460 1072 : int idx = readStaffIdx(e);
461 1072 : clefTypes[idx].transposingClef = TConv::fromXml(e.readAsciiText(), ClefType::G);
462 96720 : } else if (tag == "stafflines") {
463 0 : int idx = readStaffIdx(e);
464 0 : staffLines[idx] = e.readInt();
465 96720 : } else if (tag == "smallStaff") {
466 0 : int idx = readStaffIdx(e);
467 0 : smallStaff[idx] = e.readInt();
468 96720 : } else if (tag == "bracket") {
469 1040 : int idx = readStaffIdx(e);
470 1040 : bracket[idx] = BracketType(e.readInt());
471 95680 : } else if (tag == "bracketSpan") {
472 1040 : int idx = readStaffIdx(e);
473 1040 : bracketSpan[idx] = e.readInt();
474 94640 : } else if (tag == "barlineSpan") {
475 9632 : int idx = readStaffIdx(e);
476 9632 : int span = e.readInt();
477 10672 : for (int i = 0; i < span - 1; ++i) {
478 1040 : barlineSpan[idx + i] = true;
479 : }
480 85008 : } else if (tag == "aPitchRange") {
481 8048 : setPitchRange(e.readText(), minPitchA, maxPitchA);
482 76960 : } else if (tag == "pPitchRange") {
483 8048 : setPitchRange(e.readText(), minPitchP, maxPitchP);
484 68912 : } else if (tag == "transposition") { // obsolete
485 0 : int i = e.readInt();
486 0 : transpose.chromatic = i;
487 0 : transpose.diatonic = Interval::chromatic2diatonic(i);
488 68912 : } else if (tag == "transposeChromatic") {
489 2432 : transpose.chromatic = e.readInt();
490 66480 : } else if (tag == "transposeDiatonic") {
491 2432 : transpose.diatonic = e.readInt();
492 64048 : } else if (tag == "traitName") {
493 1968 : trait.type = traitTypeFromString(e.attribute("type"));
494 1968 : String traitName = translateInstrumentName(id, u"traitName", e.readText());
495 1968 : trait.isDefault = traitName.contains(u'*');
496 1968 : trait.isHiddenOnScore = traitName.contains(u'(') && traitName.contains(u')');
497 1968 : trait.name = traitName.remove(u'*').remove(u'(').remove(u')');
498 64048 : } else if (tag == "StringData") {
499 1216 : read400::TRead::read(&stringData, e);
500 60864 : } else if (tag == "drumset") {
501 1728 : useDrumset = e.readInt();
502 59136 : } else if (tag == "Drum") {
503 : // if we see one of these tags, a custom drumset will be created
504 4592 : if (!drumset) {
505 1712 : drumset = new Drumset(*smDrumset);
506 1712 : drumset->clear();
507 : }
508 4592 : drumset->loadDrum(e);
509 54544 : } else if (tag == "percussionPanelColumns") {
510 : // if we see one of these tags, a custom drumset will be created
511 0 : if (!drumset) {
512 0 : drumset = new Drumset(*smDrumset);
513 0 : drumset->clear();
514 : }
515 0 : drumset->setPercussionPanelColumns(e.readInt());
516 54544 : } else if (tag == "MidiAction") {
517 0 : NamedEventList a;
518 0 : read400::TRead::read(&a, e);
519 0 : midiActions.push_back(a);
520 54544 : } else if (tag == "Channel" || tag == "channel") {
521 12416 : InstrChannel a;
522 12416 : InstrumentTrackId tId;
523 12416 : read400::ReadContext rctx(nullptr);
524 12416 : read400::TRead::read(&a, e, rctx, nullptr, tId);
525 12416 : channel.push_back(a);
526 54544 : } else if (tag == "Articulation") {
527 304 : MidiArticulation a;
528 304 : read400::TRead::read(&a, e);
529 304 : size_t n = midiArticulations.size();
530 : size_t i;
531 1008 : for (i = 0; i < n; ++i) {
532 1008 : if (midiArticulations[i].name == a.name) {
533 304 : midiArticulations[i] = a;
534 304 : break;
535 : }
536 : }
537 304 : if (i == n) {
538 0 : midiArticulations.push_back(a);
539 : }
540 42128 : } else if (tag == "stafftype") {
541 2080 : int staffIdx = readStaffIdx(e);
542 2080 : String xmlPresetName = e.attribute("staffTypePreset");
543 2080 : String stfGroup = e.readText();
544 2080 : if (stfGroup == "percussion") {
545 1728 : staffGroup = StaffGroup::PERCUSSION;
546 352 : } else if (stfGroup == "tablature") {
547 352 : staffGroup = StaffGroup::TAB;
548 : } else {
549 0 : staffGroup = StaffGroup::STANDARD;
550 : }
551 2080 : staffTypePreset = 0;
552 2080 : if (!xmlPresetName.isEmpty()) {
553 2080 : staffTypePreset = StaffType::presetFromXmlName(xmlPresetName);
554 : }
555 2080 : if (!staffTypePreset || staffTypePreset->group() != staffGroup) {
556 0 : staffTypePreset = StaffType::getDefaultPreset(staffGroup);
557 : }
558 2080 : if (staffTypePreset) {
559 2080 : staffLines[staffIdx] = staffTypePreset->lines();
560 : }
561 41824 : } else if (tag == "init") {
562 368 : String val(e.readText());
563 368 : const InstrumentTemplate* ttt = searchTemplate(val);
564 368 : if (ttt) {
565 352 : String id_ = id;
566 352 : init(*ttt);
567 352 : id = id_;
568 352 : } else {
569 16 : LOGD("InstrumentTemplate:: init instrument <%s> not found", muPrintable(val));
570 : }
571 39744 : } else if (tag == "musicXMLid") {
572 9760 : musicXmlId = e.readText();
573 29616 : } else if (tag == "family") {
574 10304 : family = searchInstrumentFamily(e.readText());
575 19312 : } else if (tag == "genre") {
576 13136 : String val(e.readText());
577 13136 : linkGenre(val);
578 19312 : } else if (tag == "singleNoteDynamics") {
579 3712 : singleNoteDynamics = e.readBool();
580 2464 : } else if (tag == "glissandoStyle") {
581 2464 : glissandoStyle = TConv::fromXml(e.readAsciiText(), GlissandoStyle::CHROMATIC);
582 : } else {
583 0 : e.unknown();
584 : }
585 : }
586 10304 : if (channel.empty()) {
587 16 : InstrChannel a;
588 16 : a.setChorus(0);
589 16 : a.setReverb(0);
590 16 : a.setName(String::fromUtf8(InstrChannel::DEFAULT_NAME));
591 16 : a.setProgram(0);
592 16 : a.setBank(0);
593 16 : a.setVolume(90);
594 16 : a.setPan(0);
595 16 : channel.push_back(a);
596 16 : }
597 :
598 10304 : if (trackName.isEmpty() && !instrumentName.longName().isEmpty()) {
599 0 : trackName = instrumentName.longName();
600 : }
601 10304 : if (description.isEmpty() && !instrumentName.shortName().isEmpty()) {
602 0 : description = instrumentName.shortName();
603 : }
604 10304 : if (id.empty()) {
605 0 : id = trackName.toLower().replace(u' ', u'-');
606 : }
607 :
608 10304 : if (staffCount == 0) {
609 0 : LOGD(" 2Instrument: staves == 0 <%s>", muPrintable(id));
610 : }
611 10304 : }
612 :
613 : //---------------------------------------------------------
614 : // setPitchRange
615 : //---------------------------------------------------------
616 :
617 16096 : void InstrumentTemplate::setPitchRange(const String& s, int& a, int& b) const
618 : {
619 16096 : StringList sl = s.split(u'-');
620 16096 : if (sl.size() != 2) {
621 0 : a = MIN_PITCH;
622 0 : b = MAX_PITCH;
623 0 : return;
624 : }
625 16096 : a = sl.at(0).toInt();
626 16096 : b = sl.at(1).toInt();
627 16096 : }
628 :
629 : //---------------------------------------------------------
630 : // clearInstrumentTemplates
631 : //---------------------------------------------------------
632 :
633 0 : void clearInstrumentTemplates()
634 : {
635 0 : for (const InstrumentGroup* g : instrumentGroups) {
636 0 : const_cast<InstrumentGroup*>(g)->clear();
637 : }
638 0 : muse::DeleteAll(instrumentGroups);
639 0 : instrumentGroups.clear();
640 0 : muse::DeleteAll(instrumentGenres);
641 0 : instrumentGenres.clear();
642 0 : muse::DeleteAll(instrumentFamilies);
643 0 : instrumentFamilies.clear();
644 0 : midiArticulations.clear();
645 0 : instrumentOrders.clear();
646 0 : }
647 :
648 : //---------------------------------------------------------
649 : // loadInstrumentTemplates
650 : //---------------------------------------------------------
651 :
652 16 : bool loadInstrumentTemplates(const muse::io::path_t& instrTemplatesPath)
653 : {
654 16 : File qf(instrTemplatesPath);
655 16 : if (!qf.open(IODevice::ReadOnly)) {
656 0 : LOGE() << "Could not load instrument templates from " << instrTemplatesPath;
657 0 : return false;
658 : }
659 :
660 16 : XmlReader e(&qf);
661 48 : while (e.readNextStartElement()) {
662 16 : if (e.name() == "museScore") {
663 2464 : while (e.readNextStartElement()) {
664 2448 : const AsciiStringView tag(e.name());
665 2448 : if (tag == "instrument-group" || tag == "InstrumentGroup") {
666 192 : String idGroup(e.attribute("id"));
667 192 : InstrumentGroup* group = searchInstrumentGroup(idGroup);
668 192 : if (group == 0) {
669 192 : group = new InstrumentGroup;
670 192 : instrumentGroups.push_back(group);
671 : }
672 192 : group->read(e);
673 2448 : } else if (tag == "Articulation") {
674 : // read global articulation
675 160 : String name(e.attribute("name"));
676 160 : MidiArticulation a = searchArticulation(name);
677 160 : read400::TRead::read(&a, e);
678 160 : midiArticulations.push_back(a);
679 2256 : } else if (tag == "Genre") {
680 176 : String idGenre(e.attribute("id"));
681 176 : InstrumentGenre* genre = const_cast<InstrumentGenre*>(searchInstrumentGenre(idGenre));
682 176 : if (!genre) {
683 176 : genre = new InstrumentGenre;
684 176 : instrumentGenres.push_back(genre);
685 : }
686 176 : genre->read(e);
687 2096 : } else if (tag == "Family") {
688 1920 : String idFamily(e.attribute("id"));
689 1920 : InstrumentFamily* fam = const_cast<InstrumentFamily*>(searchInstrumentFamily(idFamily));
690 1920 : if (!fam) {
691 1920 : fam = new InstrumentFamily;
692 1920 : instrumentFamilies.push_back(fam);
693 : }
694 1920 : fam->read(e);
695 1920 : } else if (tag == "Order") {
696 0 : ScoreOrder order;
697 0 : order.read(e);
698 0 : instrumentOrders.push_back(order);
699 0 : } else {
700 0 : e.unknown();
701 : }
702 : }
703 : }
704 : }
705 :
706 16 : return true;
707 16 : }
708 :
709 14653 : const InstrumentTemplate* searchTemplate(const String& name)
710 : {
711 97563 : for (const InstrumentGroup* g : instrumentGroups) {
712 5321841 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
713 5238931 : if (it->id == name) {
714 3491 : return it;
715 : }
716 : }
717 : }
718 11162 : return 0;
719 : }
720 :
721 : // Given an unknown instrument (i.e. one that lacks a valid MuseScore ID),
722 : // compare it to each of our built-in instruments and return the strongest
723 : // available match, or `nullptr` if no match is found.
724 : //
725 : // Match strength is determined according to multiple factors (see code). Feel
726 : // free to add more factors to this function. Please don't create new functions
727 : // that only consider a subset of factors. You can disable matching against a
728 : // specific factor by setting it to its default value in the input instrument.
729 : //
730 : // Use this to recognize instruments in pre-3.6 score files, and in non-native
731 : // formats like MIDI and MusicXML (e.g. see importmusicxmlpass2.cpp). This
732 : // ensures the correct sound and engraving properties are loaded.
733 :
734 478 : const InstrumentTemplate* combinedTemplateSearch(const Instrument& instrument)
735 : {
736 478 : const InstrChannel* const channel = instrument.channel(0);
737 478 : const int bank = channel->bank();
738 478 : const int program = channel->program();
739 478 : const int transposeChromatic = instrument.transpose().chromatic;
740 478 : const int transposeKey = (transposeChromatic % 12) + (transposeChromatic < 0 ? 12 : 0); // 0=C, 1=C#/Db, 2=D...
741 478 : const String& trackName = instrument.trackName();
742 478 : const String& traitName = instrument.trait().name;
743 478 : String musicXmlId = instrument.musicXmlId();
744 :
745 478 : if (musicXmlId.isEmpty() && trackName.isEmpty() && bank == 0 && program == -1) {
746 6 : return nullptr; // Not enough information to find a matching template.
747 : }
748 :
749 472 : if (musicXmlId.isEmpty()) {
750 378 : if (trackName.contains(u"drum", muse::CaseInsensitive)) {
751 6 : musicXmlId = u"drum.group.set";
752 372 : } else if (trackName == u"MusicXML Part" || trackName.contains(u"piano", muse::CaseInsensitive)) {
753 106 : musicXmlId = u"keyboard.piano";
754 : }
755 : } else {
756 : // Workaround for old generic instrument templates.
757 186 : if ((musicXmlId == u"wind.reed.clarinet" || musicXmlId == u"brass.trumpet")
758 186 : && (traitName == u"B♭" || transposeKey == 10)
759 : ) {
760 2 : musicXmlId.append(u".bflat");
761 : }
762 : }
763 :
764 : // Perform a weighted search over instrument names. Current limitations:
765 : // * We only check in the Preferences language. (Ideally we'd check English, German & Italian.)
766 : // * We don't consider aliases (e.g. we look for "Violoncello" but not "Double Bass").
767 : static constexpr int TRACK_NAME_WEIGHT = 128;
768 : static constexpr int LONG_NAME_WEIGHT = 64;
769 : static constexpr int SHORT_NAME_WEIGHT = 16; // Not standardized outside MuseScore.
770 : static constexpr int TRAIT_NAME_WEIGHT = 4; // More reliable than transposition interval.
771 :
772 : // Also search over these parameters, which don't depend on the language.
773 : static constexpr int MUSICXML_ID_WEIGHT = 32; // Not unique. Newer, more specialist IDs aren't in old files.
774 : static constexpr int MIDI_WEIGHT = 8; // Standardization limited to General MIDI Level 2.
775 : static constexpr int TRANSPOSITION_KEY_WEIGHT = 2; // Unreliable (e.g. in concert-pitch MusicXML).
776 : static constexpr int TRANSPOSITION_OCTAVE_WEIGHT = 1;
777 :
778 : // Optimize performance: Ignore low-strength matches.
779 : static constexpr int MIN_MATCH_STRENGTH = MIDI_WEIGHT;
780 :
781 : // Also consider name almost-matches (i.e. fuzzy text search).
782 : static constexpr size_t INITIAL_LEVENSHTEIN_DISTANCE = std::numeric_limits<size_t>::max();
783 :
784 472 : const InstrumentTemplate* bestMatch = nullptr;
785 472 : int bestMatchStrength = MIN_MATCH_STRENGTH;
786 472 : auto bestMatchLevDist = INITIAL_LEVENSHTEIN_DISTANCE;
787 :
788 6136 : for (const InstrumentGroup* group : instrumentGroups) {
789 309632 : for (const InstrumentTemplate* templ : group->instrumentTemplates) {
790 303968 : if (templ->trait.name == u"[hide]") {
791 301744 : continue;
792 : }
793 :
794 303968 : int matchStrength = 0;
795 :
796 303968 : if (!trackName.isEmpty() && trackName == templ->trackName) {
797 492 : matchStrength += TRACK_NAME_WEIGHT;
798 : }
799 :
800 303968 : if (!instrument.longName().empty() && instrument.longName() == templ->instrumentName.longName()) {
801 605 : matchStrength += LONG_NAME_WEIGHT;
802 : }
803 :
804 303968 : if (!instrument.shortName().empty() && instrument.shortName() == templ->instrumentName.shortName()) {
805 3 : matchStrength += SHORT_NAME_WEIGHT;
806 : }
807 :
808 303968 : if (!traitName.isEmpty() && traitName == templ->trait.name) {
809 461 : matchStrength += TRAIT_NAME_WEIGHT;
810 : }
811 :
812 303968 : if (!musicXmlId.empty() && musicXmlId == templ->musicXmlId) {
813 349 : matchStrength += MUSICXML_ID_WEIGHT;
814 : }
815 :
816 685591 : for (const InstrChannel& templChannel : templ->channel) {
817 385831 : if (bank == templChannel.bank() && program == templChannel.program()) {
818 4208 : matchStrength += MIDI_WEIGHT;
819 4208 : break;
820 : }
821 : }
822 :
823 303968 : const int chromaticDiff = std::abs(transposeChromatic - templ->transpose.chromatic);
824 :
825 303968 : if (chromaticDiff % 12 == 0) { // Same key.
826 239064 : matchStrength += TRANSPOSITION_KEY_WEIGHT;
827 : }
828 :
829 303968 : if (chromaticDiff < 12) { // Same octave.
830 263624 : matchStrength += TRANSPOSITION_OCTAVE_WEIGHT;
831 : }
832 :
833 303968 : if (matchStrength < bestMatchStrength) {
834 301744 : continue; // Keep looking.
835 : }
836 :
837 2224 : if (matchStrength > bestMatchStrength) {
838 915 : bestMatch = templ;
839 915 : bestMatchStrength = matchStrength;
840 915 : bestMatchLevDist = INITIAL_LEVENSHTEIN_DISTANCE; // Reset.
841 : }
842 :
843 : // Find smallest lev. dist. among templates with match strength equal to best.
844 2224 : auto levDist = INITIAL_LEVENSHTEIN_DISTANCE;
845 :
846 2224 : if (!trackName.isEmpty()) {
847 2113 : levDist = muse::strings::levenshteinDistance(
848 4226 : trackName.toStdString(),
849 4226 : templ->trackName.toStdString()
850 : );
851 : }
852 :
853 : levDist
854 4448 : = std::min(levDist,
855 2224 : muse::strings::levenshteinDistance(instrument.longName().toStdString(),
856 4448 : templ->instrumentName.longName().toStdString()));
857 :
858 2224 : if (levDist < bestMatchLevDist) {
859 1146 : bestMatch = templ;
860 1146 : bestMatchLevDist = levDist;
861 : }
862 : }
863 : }
864 :
865 472 : return bestMatch;
866 478 : }
867 :
868 0 : const InstrumentTemplate* searchTemplateForMusicXmlId(const String& mxmlId)
869 : {
870 0 : for (const InstrumentGroup* g : instrumentGroups) {
871 0 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
872 0 : if (it->musicXmlId == mxmlId) {
873 0 : return it;
874 : }
875 : }
876 : }
877 0 : return 0;
878 : }
879 :
880 952 : const InstrumentTemplate* searchTemplateForInstrNameList(const std::vector<String>& nameList, bool useDrumset, bool caseSensitive)
881 : {
882 952 : const InstrumentTemplate* bestMatch = nullptr; // default if no matches
883 952 : int bestMatchStrength = 0; // higher for better matches
884 :
885 3734 : for (const String& name : nameList) {
886 2782 : if (name.isEmpty()) {
887 1014 : continue;
888 : }
889 :
890 : auto stringEqualsName = caseSensitive
891 921452 : ? std::function([&name](const String& s) { return s == name; })
892 5764 : : std::function([&name](const String& s) { return s.isEqualIgnoreCase(name); });
893 :
894 : auto staffNameEqualsName = caseSensitive
895 1841136 : ? std::function([&name](const String& sn) { return sn == name; })
896 9760 : : std::function([&name](const String& sn) { return sn.isEqualIgnoreCase(name); });
897 :
898 22984 : for (const InstrumentGroup* g : instrumentGroups) {
899 1159808 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
900 1138592 : if (it->useDrumset != useDrumset) {
901 214912 : continue;
902 : }
903 :
904 : int matchStrength
905 : = 0
906 923680 : + (4 * (stringEqualsName(it->trackName) ? 1 : 0)) // most weight to track name since there are fewer duplicates
907 923680 : + (2 * (staffNameEqualsName(it->instrumentName.longName()) ? 1 : 0))
908 923680 : + (1 * (staffNameEqualsName(it->instrumentName.shortName()) ? 1 : 0)); // least weight to short name
909 923680 : const int perfectMatchStrength = 7;
910 923680 : assert(matchStrength <= perfectMatchStrength);
911 923680 : if (matchStrength > bestMatchStrength) {
912 559 : bestMatch = it;
913 559 : bestMatchStrength = matchStrength;
914 559 : if (bestMatchStrength == perfectMatchStrength) {
915 0 : break; // stop looking for matches
916 : }
917 : }
918 : }
919 : }
920 1768 : }
921 :
922 952 : if (!bestMatch) {
923 393 : static const std::wregex drumsetRegex(L"drum ?(set|kit)", std::regex_constants::icase);
924 :
925 1471 : for (const String& name : nameList) {
926 1092 : if (name.contains(drumsetRegex)) {
927 3 : return searchTemplate(u"drumset"); // Large Drum Kit
928 : }
929 :
930 1089 : if (name.contains(u"drum", muse::CaseInsensitive) || name.contains(u"percussion", muse::CaseInsensitive)) {
931 4 : return searchTemplate(u"percussion-synthesizer"); // General MIDI percussion
932 : }
933 :
934 1085 : if (name.contains(u"piano", muse::CaseInsensitive)) {
935 7 : return searchTemplate(u"piano");
936 : }
937 : }
938 : }
939 :
940 938 : return bestMatch; // nullptr if no matches found
941 : }
942 :
943 344 : const InstrumentTemplate* searchTemplateForMidiProgram(int bank, int program, bool useDrumset)
944 : {
945 3759 : for (const InstrumentGroup* g : instrumentGroups) {
946 186016 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
947 182601 : if (it->useDrumset != useDrumset) {
948 36612 : continue;
949 : }
950 :
951 321654 : for (const InstrChannel& channel : it->channel) {
952 175986 : if (channel.bank() == bank && channel.program() == program) {
953 321 : return it;
954 : }
955 : }
956 : }
957 : }
958 :
959 23 : return nullptr;
960 : }
961 :
962 0 : void addTemplateToGroup(const InstrumentTemplate* templ, const String& groupId)
963 : {
964 0 : IF_ASSERT_FAILED(templ) {
965 0 : return;
966 : }
967 :
968 0 : InstrumentGroup* group = searchInstrumentGroup(groupId);
969 0 : if (group) {
970 0 : group->instrumentTemplates.push_back(templ);
971 : }
972 : }
973 :
974 : //---------------------------------------------------------
975 : // searchTemplateIndexForTrackName
976 : //---------------------------------------------------------
977 :
978 0 : InstrumentIndex searchTemplateIndexForTrackName(const String& trackName)
979 : {
980 0 : int instIndex = 0;
981 0 : int grpIndex = 0;
982 0 : for (const InstrumentGroup* g : instrumentGroups) {
983 0 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
984 0 : if (it->trackName == trackName) {
985 0 : return InstrumentIndex(grpIndex, instIndex, it);
986 : }
987 0 : ++instIndex;
988 : }
989 0 : ++grpIndex;
990 : }
991 0 : return InstrumentIndex(-1, -1, nullptr);
992 : }
993 :
994 : //---------------------------------------------------------
995 : // searchTemplateIndexForId
996 : //---------------------------------------------------------
997 :
998 1036 : InstrumentIndex searchTemplateIndexForId(const String& id)
999 : {
1000 1036 : int instIndex = 0;
1001 1036 : int grpIndex = 0;
1002 8178 : for (const InstrumentGroup* g : instrumentGroups) {
1003 424397 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
1004 417255 : if (it->id == id) {
1005 1015 : return InstrumentIndex(grpIndex, instIndex, it);
1006 : }
1007 416240 : ++instIndex;
1008 : }
1009 7142 : ++grpIndex;
1010 : }
1011 21 : return InstrumentIndex(-1, instIndex, nullptr);
1012 : }
1013 :
1014 : //---------------------------------------------------------
1015 : // linkGenre
1016 : // link the current instrument template to the genre list specified by "genre"
1017 : // Each genre is a list of pointers to instrument templates
1018 : // The list of genres is at application level
1019 : //---------------------------------------------------------
1020 :
1021 13136 : void InstrumentTemplate::linkGenre(const String& genre)
1022 : {
1023 13136 : const InstrumentGenre* ig = searchInstrumentGenre(genre);
1024 13136 : if (ig) {
1025 13136 : genres.push_back(ig);
1026 : }
1027 13136 : }
1028 :
1029 0 : void InstrumentGenre::write(XmlWriter& xml) const
1030 : {
1031 0 : xml.startElement("Genre", { { "id", id } });
1032 0 : xml.tag("name", name);
1033 0 : xml.endElement();
1034 0 : }
1035 :
1036 176 : void InstrumentGenre::read(XmlReader& e)
1037 : {
1038 176 : id = e.attribute("id");
1039 352 : while (e.readNextStartElement()) {
1040 176 : const AsciiStringView tag(e.name());
1041 176 : if (tag == "name") {
1042 176 : name = muse::mtrc("engraving/instruments/genre", e.readText());
1043 : } else {
1044 0 : e.unknown();
1045 : }
1046 : }
1047 176 : }
1048 :
1049 0 : void InstrumentFamily::write(XmlWriter& xml) const
1050 : {
1051 0 : xml.startElement("Family", { { "id", id } });
1052 0 : xml.tag("name", name);
1053 0 : xml.endElement();
1054 0 : }
1055 :
1056 1920 : void InstrumentFamily::read(XmlReader& e)
1057 : {
1058 1920 : id = e.attribute("id");
1059 3840 : while (e.readNextStartElement()) {
1060 1920 : const AsciiStringView tag(e.name());
1061 1920 : if (tag == "name") {
1062 1920 : name = muse::mtrc("engraving/instruments/family", e.readText());
1063 : } else {
1064 0 : e.unknown();
1065 : }
1066 : }
1067 1920 : }
1068 :
1069 : //---------------------------------------------------------
1070 : // clefType
1071 : //---------------------------------------------------------
1072 :
1073 18 : ClefTypeList InstrumentTemplate::clefType(staff_idx_t staffIdx) const
1074 : {
1075 18 : if (staffIdx < staffCount) {
1076 18 : return clefTypes[staffIdx];
1077 : }
1078 0 : return clefTypes[0];
1079 : }
1080 :
1081 627 : String InstrumentTemplate::familyId() const
1082 : {
1083 627 : return family ? family->id : String();
1084 : }
1085 :
1086 0 : bool InstrumentTemplate::containsGenre(const String& genreId) const
1087 : {
1088 0 : for (const InstrumentGenre* genre : genres) {
1089 0 : if (genre->id == genreId) {
1090 0 : return true;
1091 : }
1092 : }
1093 :
1094 0 : return false;
1095 : }
1096 :
1097 : //---------------------------------------------------------
1098 : // defaultClef
1099 : // traverse the instrument list for first instrument
1100 : // with midi patch 'program'. Return the default clef
1101 : // for this instrument.
1102 : //---------------------------------------------------------
1103 :
1104 77 : ClefType defaultClef(int program)
1105 : {
1106 77 : if (program >= 24 && program < 32) { // this are guitars
1107 71 : return ClefType::G8_VB;
1108 6 : } else if (program >= 32 && program < 40) { // this is bass
1109 2 : return ClefType::F8_VB;
1110 : }
1111 :
1112 31 : for (const InstrumentGroup* g : instrumentGroups) {
1113 1811 : for (const InstrumentTemplate* it : g->instrumentTemplates) {
1114 1784 : if (it->channel[0].bank() == 0 && it->channel[0].program() == program) {
1115 4 : return it->clefTypes[0].concertClef;
1116 : }
1117 : }
1118 : }
1119 0 : return ClefType::G;
1120 : }
1121 : }
|