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 "fret.h"
24 :
25 : #include "translation.h"
26 :
27 : #include "io/file.h"
28 :
29 : #include "../editing/addremoveelement.h"
30 : #include "../editing/editfretboarddiagram.h"
31 :
32 : #include "anchors.h"
33 : #include "chord.h"
34 : #include "factory.h"
35 : #include "harmony.h"
36 : #include "measure.h"
37 : #include "note.h"
38 : #include "pitchspelling.h"
39 : #include "score.h"
40 : #include "segment.h"
41 : #include "staff.h"
42 : #include "stringdata.h"
43 : #include "system.h"
44 :
45 : #include "rw/harmonytodiagramreader.h"
46 : #include "rw/read460/tread.h"
47 :
48 : #include "log.h"
49 :
50 : using namespace mu;
51 : using namespace muse::draw;
52 : using namespace mu::engraving;
53 :
54 : namespace mu::engraving {
55 : // parent() is Segment or Box
56 : //
57 :
58 : //---------------------------------------------------------
59 : // fretStyle
60 : //---------------------------------------------------------
61 :
62 : static const ElementStyle fretStyle {
63 : { Sid::fretNumPos, Pid::FRET_NUM_POS },
64 : { Sid::fretMag, Pid::MAG },
65 : { Sid::fretPlacement, Pid::PLACEMENT },
66 : { Sid::fretStrings, Pid::FRET_STRINGS },
67 : { Sid::fretFrets, Pid::FRET_FRETS },
68 : { Sid::fretNut, Pid::FRET_NUT },
69 : { Sid::fretMinDistance, Pid::MIN_DISTANCE },
70 : { Sid::fretOrientation, Pid::ORIENTATION },
71 : { Sid::fretShowFingerings, Pid::FRET_SHOW_FINGERINGS },
72 : };
73 :
74 : //---------------------------------------------------------
75 : // FretDiagram
76 : //---------------------------------------------------------
77 :
78 : struct HarmonyMapKey
79 : {
80 : HarmonyMapKey() {}
81 6353 : HarmonyMapKey(int k, int r, int b)
82 6353 : : keys(k), rootTpc(r), bassTpc(b) {}
83 :
84 : int keys = 0;
85 : int rootTpc = Tpc::TPC_INVALID;
86 : int bassTpc = Tpc::TPC_INVALID;
87 :
88 85780 : bool operator<(const HarmonyMapKey& other) const
89 : {
90 85780 : return std::tie(keys, rootTpc, bassTpc) < std::tie(other.keys, other.rootTpc, other.bassTpc);
91 : }
92 :
93 : bool operator==(const HarmonyMapKey& other) const
94 : {
95 : return std::tie(keys, rootTpc, bassTpc) == std::tie(other.keys, other.rootTpc, other.bassTpc);
96 : }
97 : };
98 :
99 : static std::map<HarmonyMapKey /*key*/, std::vector<DiagramInfo> > s_harmonyToDiagramMap;
100 : static std::unordered_map<String /*pattern*/, std::vector<String /*harmonyName*/> > s_diagramPatternToHarmoniesMap;
101 :
102 : static const muse::io::path_t HARMONY_TO_DIAGRAM_FILE_PATH(":/data/harmony_to_diagram.xml");
103 :
104 9865 : static int canonicalEnharmonicTpc(int tpc)
105 : {
106 : // Normalize to one canonical TPC per pitch class
107 9865 : int mod = (tpc - Tpc::TPC_G_B) % TPC_DELTA_ENHARMONIC;
108 9865 : return Tpc::TPC_G_B + (mod < 0 ? mod + TPC_DELTA_ENHARMONIC : mod);
109 : }
110 :
111 6353 : static HarmonyMapKey createHarmonyMapKey(const String& harmony, const NoteSpellingType& spellingType, const ChordList* cl)
112 : {
113 6353 : String s = harmony;
114 : NoteCaseType noteCase;
115 : size_t idx;
116 6353 : int rootTpc = convertNote(s, spellingType, noteCase, idx);
117 :
118 6353 : int bassTpc = Tpc::TPC_INVALID;
119 6353 : size_t slash = s.lastIndexOf(u'/');
120 6353 : if (slash != muse::nidx) {
121 3534 : String bs = s.mid(slash + 1).simplified();
122 3534 : s = s.mid(idx, slash - idx).simplified();
123 3534 : size_t idx2 = 0;
124 3534 : bassTpc = convertNote(bs, spellingType, noteCase, idx2);
125 :
126 3534 : if (!tpcIsValid(bassTpc)) {
127 : // if what follows after slash is not (just) a TPC
128 : // then reassemble chord and try to parse with the slash
129 20 : s = s + u"/" + bs;
130 : }
131 3534 : } else {
132 2819 : s = s.mid(idx);
133 : }
134 :
135 6353 : ParsedChord chord;
136 6353 : if (!chord.parse(s, cl)) {
137 0 : LOGE() << "Error parse " << harmony;
138 : }
139 :
140 6353 : int keys = chord.keys();
141 :
142 12706 : return HarmonyMapKey(keys, rootTpc, bassTpc);
143 6353 : }
144 :
145 69 : static DiagramInfo resolveDiagram(const std::vector<DiagramInfo>& diagrams)
146 : {
147 47 : auto isSimpler = [](const DiagramInfo& a, const DiagramInfo& b) {
148 47 : const String& harmonyNameA = a.harmonyName;
149 47 : const String& harmonyNameB = b.harmonyName;
150 :
151 47 : if (harmonyNameA.size() != harmonyNameB.size()) {
152 35 : return harmonyNameA.size() < harmonyNameB.size();
153 : }
154 :
155 12 : return harmonyNameA < harmonyNameB;
156 : };
157 :
158 69 : DiagramInfo result = diagrams.front();
159 116 : for (size_t i = 1; i < diagrams.size(); ++i) {
160 47 : if (!isSimpler(result, diagrams[i])) {
161 26 : result = diagrams[i];
162 : }
163 : }
164 :
165 69 : return result;
166 0 : }
167 :
168 130 : FretDiagram::FretDiagram(DummyParentOr<Segment> parent)
169 260 : : EngravingItem(ElementType::FRET_DIAGRAM, parent, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
170 : {
171 130 : initDefaultValues();
172 :
173 130 : initElementStyle(&fretStyle);
174 130 : }
175 :
176 22 : FretDiagram::FretDiagram(const FretDiagram& f)
177 44 : : EngravingItem(f)
178 : {
179 22 : m_strings = f.m_strings;
180 22 : m_frets = f.m_frets;
181 22 : m_fretOffset = f.m_fretOffset;
182 22 : m_maxFrets = f.m_maxFrets;
183 22 : m_userMag = f.m_userMag;
184 22 : m_dots = f.m_dots;
185 22 : m_markers = f.m_markers;
186 22 : m_barres = f.m_barres;
187 22 : m_showNut = f.m_showNut;
188 22 : m_orientation= f.m_orientation;
189 22 : m_showFingering = f.m_showFingering;
190 22 : m_fingering = f.m_fingering;
191 :
192 22 : if (f.m_harmony) {
193 22 : Harmony* h = new Harmony(*f.m_harmony);
194 22 : add(h);
195 : }
196 22 : }
197 :
198 130 : void FretDiagram::initDefaultValues()
199 : {
200 130 : m_strings = 6;
201 130 : m_frets = 4;
202 130 : m_fretOffset = 0;
203 130 : m_maxFrets = 24;
204 130 : m_showNut = true;
205 130 : m_orientation = Orientation::VERTICAL;
206 :
207 130 : m_userMag = 1.0;
208 :
209 130 : m_showFingering = false;
210 130 : m_fingering = std::vector<int>(m_strings, 0);
211 130 : }
212 :
213 300 : FretDiagram::~FretDiagram()
214 : {
215 150 : if (m_harmony) {
216 85 : delete m_harmony;
217 : }
218 300 : }
219 :
220 : //---------------------------------------------------------
221 : // linkedClone
222 : //---------------------------------------------------------
223 :
224 0 : EngravingItem* FretDiagram::linkedClone()
225 : {
226 0 : FretDiagram* e = clone();
227 0 : e->setAutoplace(true);
228 0 : if (m_harmony) {
229 0 : EngravingItem* newHarmony = m_harmony->linkedClone();
230 0 : e->add(newHarmony);
231 : }
232 0 : score()->undo(new Link(e, this));
233 0 : return e;
234 : }
235 :
236 701 : Segment* FretDiagram::segment() const
237 : {
238 701 : EngravingObject* parent = ownershipParent();
239 701 : if (!parent || !parent->isSegment()) {
240 0 : return nullptr;
241 : }
242 :
243 701 : return toSegment(ownershipParent());
244 : }
245 :
246 69 : void FretDiagram::updateDiagram(const String& harmonyName)
247 : {
248 69 : std::vector<DiagramInfo> availableDiagrams = patternsFromHarmony(harmonyName);
249 69 : if (availableDiagrams.empty()) {
250 0 : return;
251 : }
252 :
253 69 : String diagramXml = resolveDiagram(availableDiagrams).diagramXml;
254 :
255 69 : if (diagramXml.empty()) {
256 0 : return;
257 : }
258 :
259 69 : clear();
260 :
261 69 : read460::ReadContext ctx(score());
262 69 : XmlReader reader(diagramXml.toUtf8());
263 :
264 69 : read460::TRead::read(this, reader, ctx);
265 :
266 69 : triggerLayout();
267 69 : return;
268 69 : }
269 :
270 1391 : double FretDiagram::mainWidth() const
271 : {
272 1391 : double mainWidth = 0.0;
273 1391 : if (orientation() == Orientation::VERTICAL) {
274 1391 : mainWidth = ldata()->stringDist * (strings() - 1);
275 0 : } else if (orientation() == Orientation::HORIZONTAL) {
276 0 : mainWidth = ldata()->fretDist * frets();
277 : }
278 1391 : return mainWidth;
279 : }
280 :
281 : //---------------------------------------------------------
282 : // dragAnchorLines
283 : //---------------------------------------------------------
284 :
285 0 : std::vector<LineF> FretDiagram::dragAnchorLines() const
286 : {
287 0 : return genericDragAnchorLines();
288 : }
289 :
290 : //---------------------------------------------------------
291 : // setStrings
292 : //---------------------------------------------------------
293 :
294 166 : void FretDiagram::setStrings(int n)
295 : {
296 166 : int difference = n - m_strings;
297 166 : if (difference == 0 || n <= 0) {
298 166 : return;
299 : }
300 :
301 : // Move all dots, makers, barres to the RIGHT, so we add strings to the left
302 : // This is more useful - few instruments need strings added to the right.
303 0 : DotMap tempDots;
304 0 : MarkerMap tempMarkers;
305 :
306 0 : for (int string = 0; string < m_strings; ++string) {
307 0 : if (string + difference < 0) {
308 0 : continue;
309 : }
310 :
311 0 : for (auto const& d : dot(string)) {
312 0 : if (d.exists()) {
313 0 : tempDots[string + difference].push_back(FretItem::Dot(d));
314 : }
315 0 : }
316 :
317 0 : if (marker(string).exists()) {
318 0 : tempMarkers[string + difference] = marker(string);
319 : }
320 : }
321 :
322 0 : m_dots = tempDots;
323 0 : m_markers = tempMarkers;
324 :
325 0 : for (int fret = 1; fret <= m_frets; ++fret) {
326 0 : if (barre(fret).exists()) {
327 0 : if (m_barres[fret].startString + difference <= 0) {
328 0 : removeBarre(fret);
329 0 : continue;
330 : }
331 :
332 0 : m_barres[fret].startString = std::max(0, m_barres[fret].startString + difference);
333 0 : m_barres[fret].endString = m_barres[fret].endString == -1 ? -1 : m_barres[fret].endString + difference;
334 : }
335 : }
336 :
337 0 : if (difference > 0) {
338 0 : for (int i = 0; i < difference; ++i) {
339 0 : m_fingering.push_back(0);
340 : }
341 : } else {
342 0 : m_fingering.erase(m_fingering.end() + difference, m_fingering.end());
343 : }
344 :
345 0 : m_strings = n;
346 0 : }
347 :
348 : //---------------------------------------------------------
349 : // init
350 : //---------------------------------------------------------
351 :
352 0 : void FretDiagram::init(StringData* stringData, Chord* chord)
353 : {
354 0 : if (!stringData) {
355 0 : setStrings(6);
356 : } else {
357 0 : setStrings(static_cast<int>(stringData->strings()));
358 : }
359 0 : if (stringData) {
360 0 : for (int string = 0; string < m_strings; ++string) {
361 0 : setMarker(string, FretMarkerType::CROSS);
362 : }
363 0 : for (const Note* note : chord->notes()) {
364 : int string;
365 : int fret;
366 0 : if (stringData->convertPitch(note->pitch(), chord->staff(), &string, &fret)) {
367 0 : setDot(string, fret);
368 : }
369 : }
370 0 : m_frets = stringData->frets();
371 : } else {
372 0 : m_maxFrets = 6;
373 : }
374 0 : }
375 :
376 : //---------------------------------------------------------
377 : // setDot
378 : // take a fret value of 0 to mean remove the dot, except with add
379 : // where we actually need to pass a fret val.
380 : //---------------------------------------------------------
381 :
382 456 : void FretDiagram::setDot(int string, int fret, bool add /*= false*/, FretDotType dtype /*= FretDotType::NORMAL*/)
383 : {
384 456 : if (fret == 0) {
385 0 : removeDot(string, fret);
386 456 : } else if (string >= 0 && string < m_strings) {
387 : // Special case - with add, if there is a dot in the position, remove it
388 : // If not, add it.
389 456 : if (add) {
390 369 : if (dot(string, fret)[0].exists()) {
391 0 : removeDot(string, fret);
392 0 : return; // We are done here, all we needed to do was remove a single dot
393 : }
394 : } else {
395 87 : m_dots[string].clear();
396 : }
397 :
398 456 : m_dots[string].push_back(FretItem::Dot(fret, dtype));
399 456 : if (!add) {
400 87 : setMarker(string, FretMarkerType::NONE);
401 : }
402 : }
403 : }
404 :
405 0 : void FretDiagram::addDotForDotStyleBarre(int string, int fret)
406 : {
407 0 : if (m_dots[string].empty()) {
408 0 : m_dots[string].push_back(FretItem::Dot(fret, FretDotType::NORMAL, /*isPartOfSlurBarre*/ true));
409 : }
410 0 : }
411 :
412 1139 : void FretDiagram::removeDotForDotStyleBarre(int string, int fret)
413 : {
414 1139 : removeDot(string, fret);
415 1139 : }
416 :
417 : //---------------------------------------------------------
418 : // setMarker
419 : // Removal of dots and barres if "Multiple dots" is inactive
420 : // is handled in FretCanvas::mousePressEvent()
421 : //---------------------------------------------------------
422 :
423 554 : void FretDiagram::setMarker(int string, FretMarkerType mtype)
424 : {
425 554 : if (string >= 0 && string < m_strings) {
426 554 : m_markers[string] = FretItem::Marker(mtype);
427 554 : if (mtype != FretMarkerType::NONE) {
428 431 : removeDot(string);
429 431 : removeBarres(string);
430 : }
431 : }
432 554 : }
433 :
434 : //---------------------------------------------------------
435 : // setBarre
436 : // We'll accept a value of -1 for the end string, to denote
437 : // that the barre goes as far right as possible.
438 : // Take a start string value of -1 to mean 'remove this barre'
439 : //---------------------------------------------------------
440 :
441 35 : void FretDiagram::setBarre(int startString, int endString, int fret)
442 : {
443 35 : if (startString == -1) {
444 0 : removeBarre(fret);
445 35 : } else if (startString >= 0 && endString >= -1 && startString < m_strings && endString < m_strings) {
446 35 : m_barres[fret] = FretItem::Barre(startString, endString);
447 : }
448 35 : }
449 :
450 : //---------------------------------------------------------
451 : // This version is for clicks on a dot with shift.
452 : // If there is no barre at fret, then add one with the string as the start.
453 : // If there is a barre with a -1 end string, set the end string to string.
454 : // If there is a barre with a set start and end, remove it.
455 : // Add may be used in the future if we decide to add dots as default with barres.
456 : //---------------------------------------------------------
457 :
458 1 : void FretDiagram::setBarre(int string, int fret, bool add /*= false*/)
459 : {
460 : UNUSED(add);
461 :
462 1 : FretItem::Barre b = barre(fret);
463 1 : if (!b.exists()) {
464 1 : if (string < m_strings - 1) {
465 1 : m_barres[fret] = FretItem::Barre(string, -1);
466 1 : removeDotsMarkers(string, -1, fret);
467 : }
468 0 : } else if (b.endString == -1 && b.startString < string) {
469 0 : m_barres[fret].endString = string;
470 : } else {
471 0 : removeDotsMarkers(b.startString, b.endString, fret);
472 0 : removeBarre(fret);
473 : }
474 1 : }
475 :
476 : //---------------------------------------------------------
477 : // undoSetFretDot
478 : //---------------------------------------------------------
479 :
480 1 : void FretDiagram::undoSetFretDot(int _string, int _fret, bool _add /*= true*/, FretDotType _dtype /*= FretDotType::NORMAl*/)
481 : {
482 2 : for (EngravingObject* e : linkList()) {
483 1 : FretDiagram* fd = toFretDiagram(e);
484 1 : fd->score()->undo(new FretDot(fd, _string, _fret, _add, _dtype));
485 1 : }
486 1 : }
487 :
488 : //---------------------------------------------------------
489 : // undoSetFretMarker
490 : //---------------------------------------------------------
491 :
492 1 : void FretDiagram::undoSetFretMarker(int _string, FretMarkerType _mtype)
493 : {
494 2 : for (EngravingObject* e : linkList()) {
495 1 : FretDiagram* fd = toFretDiagram(e);
496 1 : fd->score()->undo(new FretMarker(fd, _string, _mtype));
497 1 : }
498 1 : }
499 :
500 : //---------------------------------------------------------
501 : // undoSetFretBarre
502 : // add refers to using multiple dots per string when adding dots automatically
503 : //---------------------------------------------------------
504 :
505 1 : void FretDiagram::undoSetFretBarre(int _string, int _fret, bool _add /*= false*/)
506 : {
507 2 : for (EngravingObject* e : linkList()) {
508 1 : FretDiagram* fd = toFretDiagram(e);
509 1 : fd->score()->undo(new FretBarre(fd, _string, _fret, _add));
510 1 : }
511 1 : }
512 :
513 : //---------------------------------------------------------
514 : // removeBarre
515 : // Remove a barre on a given fret.
516 : //---------------------------------------------------------
517 :
518 0 : void FretDiagram::removeBarre(int f)
519 : {
520 0 : m_barres.erase(f);
521 0 : }
522 :
523 : //---------------------------------------------------------
524 : // removeBarres
525 : // Remove barres crossing a certain point. Fret of 0 means any point along
526 : // the string.
527 : //---------------------------------------------------------
528 :
529 431 : void FretDiagram::removeBarres(int string, int fret /*= 0*/)
530 : {
531 431 : auto iter = m_barres.begin();
532 431 : while (iter != m_barres.end()) {
533 0 : int bfret = iter->first;
534 0 : FretItem::Barre b = iter->second;
535 :
536 0 : if (b.exists() && b.startString <= string && (b.endString >= string || b.endString == -1)) {
537 0 : if (fret > 0 && fret != bfret) {
538 0 : ++iter;
539 : } else {
540 0 : iter = m_barres.erase(iter);
541 : }
542 : } else {
543 0 : ++iter;
544 : }
545 : }
546 431 : }
547 :
548 : //---------------------------------------------------------
549 : // removeMarker
550 : //---------------------------------------------------------
551 :
552 0 : void FretDiagram::removeMarker(int s)
553 : {
554 0 : auto it = m_markers.find(s);
555 0 : m_markers.erase(it);
556 0 : }
557 :
558 : //---------------------------------------------------------
559 : // removeDot
560 : // take a fret value of 0 to mean remove all dots
561 : //---------------------------------------------------------
562 :
563 1577 : void FretDiagram::removeDot(int s, int f /*= 0*/)
564 : {
565 1577 : if (f > 0) {
566 1146 : std::vector<FretItem::Dot> tempDots;
567 2292 : for (auto const& d : dot(s)) {
568 1146 : if (d.exists() && d.fret != f) {
569 615 : tempDots.push_back(FretItem::Dot(d));
570 : }
571 1146 : }
572 :
573 1146 : m_dots[s] = tempDots;
574 1146 : } else {
575 431 : m_dots[s].clear();
576 : }
577 :
578 1577 : if (m_dots[s].size() == 0) {
579 962 : auto it = m_dots.find(s);
580 962 : m_dots.erase(it);
581 : }
582 1577 : }
583 :
584 : //---------------------------------------------------------
585 : // removeDotsMarkers
586 : // removes all markers between [ss, es] and dots between [ss, es],
587 : // where the dots have a fret of fret.
588 : //---------------------------------------------------------
589 :
590 1 : void FretDiagram::removeDotsMarkers(int ss, int es, int fret)
591 : {
592 1 : if (ss == -1) {
593 0 : return;
594 : }
595 :
596 1 : int end = es == -1 ? m_strings : es;
597 8 : for (int string = ss; string <= end; ++string) {
598 7 : removeDot(string, fret);
599 :
600 7 : if (marker(string).exists()) {
601 0 : removeMarker(string);
602 : }
603 : }
604 : }
605 :
606 : //---------------------------------------------------------
607 : // Fill diagram from string like "XO-[1-O][1-X,2-O,3-S][3-T];B1[0-5];B..."
608 : // - Each character or bracketed block represents a string, from lowest to highest.
609 : // - 'X' = muted string (cross marker)
610 : // - 'O' = open string (circle marker)
611 : // - '-' = empty or unused string
612 : // - [fret-type,...] = one or more fretted dots on that string:
613 : // • fret is absolute (already includes offset)
614 : // • type is:
615 : // - O = circle
616 : // - X = cross
617 : // - S = square
618 : // - T = triangle
619 : // - Example: [1-X,2-O,3-S] means three dots on frets 1, 2, and 3 with different types
620 : // - Barre chords follow after ';', in the format: B{fret}[{start}-{end}]
621 : // e.g. B1[0-5] = barre on fret 1 from string 0 to 5
622 : //---------------------------------------------------------
623 :
624 0 : void FretDiagram::applyDiagramPattern(FretDiagram* diagram, const String& pattern)
625 : {
626 0 : diagram->clear();
627 :
628 0 : const std::vector<String> parts = pattern.split(';');
629 0 : if (parts.empty()) {
630 0 : return;
631 : }
632 :
633 0 : const String& mainPart = parts[0];
634 0 : std::vector<String> stringTokens;
635 :
636 0 : for (size_t i = 0; i < mainPart.size();) {
637 0 : if (mainPart[i] == u'[') {
638 0 : size_t rb = mainPart.indexOf(u']', i);
639 0 : if (rb != muse::nidx) {
640 0 : stringTokens.push_back(mainPart.mid(i, rb - i + 1));
641 0 : i = rb + 1;
642 0 : continue;
643 : }
644 : }
645 0 : stringTokens.push_back(mainPart.mid(i, 1));
646 0 : ++i;
647 : }
648 :
649 0 : const int stringCount = static_cast<int>(stringTokens.size());
650 0 : diagram->setStrings(stringCount);
651 0 : diagram->setFrets(4);
652 0 : diagram->setPropertyFlags(Pid::FRET_STRINGS, PropertyFlags::UNSTYLED);
653 0 : diagram->setPropertyFlags(Pid::FRET_FRETS, PropertyFlags::UNSTYLED);
654 :
655 0 : int offset = 0;
656 :
657 0 : for (int i = 0; i < stringCount; ++i) {
658 0 : const String& token = stringTokens[i];
659 :
660 0 : if (token == u"-") {
661 0 : continue;
662 : }
663 :
664 0 : if (token.startsWith(u"[") && token.endsWith(u"]")) {
665 : // Example: [1-O,2-X,3-S]
666 0 : String inner = token.mid(1, token.size() - 2);
667 0 : std::vector<String> pairs = inner.split(u',');
668 :
669 0 : for (const String& p : pairs) {
670 0 : size_t dash = p.indexOf(u'-');
671 0 : if (dash > 0) {
672 0 : int fret = p.left(dash).toInt();
673 0 : Char typeChar = p.mid(dash + 1, 1).at(0);
674 : FretDotType dt;
675 :
676 0 : switch (typeChar.unicode()) {
677 0 : case 'X': dt = FretDotType::CROSS;
678 0 : break;
679 0 : case 'O': dt = FretDotType::NORMAL;
680 0 : break;
681 0 : case 'S': dt = FretDotType::SQUARE;
682 0 : break;
683 0 : case 'T': dt = FretDotType::TRIANGLE;
684 0 : break;
685 0 : default: dt = FretDotType::NORMAL;
686 0 : break;
687 : }
688 :
689 0 : diagram->setDot(i, fret - offset, true, dt);
690 0 : if (fret - 3 > 0 && offset < fret - 3) {
691 0 : offset = fret - 3;
692 : }
693 : }
694 : }
695 0 : } else if (token == u"X") {
696 0 : diagram->setMarker(i, FretMarkerType::CROSS);
697 0 : } else if (token == u"O") {
698 0 : diagram->setMarker(i, FretMarkerType::CIRCLE);
699 : }
700 : }
701 :
702 0 : if (offset > 0) {
703 0 : diagram->setFretOffset(offset);
704 : }
705 :
706 0 : for (size_t i = 1; i < parts.size(); ++i) {
707 0 : const String& barrePart = parts[i];
708 0 : if (!barrePart.startsWith(u'B')) {
709 0 : continue;
710 : }
711 :
712 0 : size_t lb = barrePart.indexOf(u'[');
713 0 : size_t rb = barrePart.indexOf(u']');
714 0 : if (lb < 2 || rb < lb) {
715 0 : continue;
716 : }
717 :
718 0 : int fret = barrePart.mid(1, lb - 1).toInt();
719 0 : if (fret <= 0) {
720 0 : continue;
721 : }
722 :
723 0 : String range = barrePart.mid(lb + 1, rb - lb - 1);
724 0 : size_t dash = range.indexOf('-');
725 0 : if (dash > 0) {
726 0 : int start = range.left(dash).toInt();
727 0 : int end = range.mid(dash + 1).toInt();
728 0 : if (start >= 0 && end >= start && end < stringCount) {
729 0 : diagram->setBarre(start, end, fret - offset);
730 : }
731 : }
732 0 : }
733 0 : }
734 :
735 56 : String FretDiagram::patternFromDiagram() const
736 : {
737 56 : const int diagramStrings = strings();
738 56 : const int offset = fretOffset();
739 :
740 56 : StringList patternParts;
741 56 : const DotMap& dotsMap = dots();
742 :
743 392 : for (int i = 0; i < diagramStrings; ++i) {
744 336 : const FretItem::Marker fretMarker = marker(i);
745 :
746 336 : if (fretMarker.mtype == FretMarkerType::CROSS) {
747 25 : patternParts.push_back(u"X");
748 79 : continue;
749 311 : } else if (fretMarker.mtype == FretMarkerType::CIRCLE) {
750 54 : patternParts.push_back(u"O");
751 54 : continue;
752 : }
753 :
754 257 : const auto it = dotsMap.find(i);
755 257 : std::vector<FretItem::Dot> dotList;
756 257 : if (it != dotsMap.end()) {
757 75 : const std::vector<FretItem::Dot>& dots = it->second;
758 150 : for (const FretItem::Dot& dot : dots) {
759 75 : if (!dot.isPartOfSlurBarre) { // Don't write dot if part of slur barré
760 75 : dotList.push_back(dot);
761 : }
762 : }
763 : }
764 :
765 257 : if (!dotList.empty()) {
766 75 : const auto& dotList2 = it->second;
767 75 : StringList dotDescriptions;
768 150 : for (const auto& dot : dotList2) {
769 75 : if (dot.isPartOfSlurBarre) {
770 0 : continue;
771 : }
772 :
773 75 : int actualFret = dot.fret + offset;
774 75 : Char typeChar = u'O';
775 :
776 75 : switch (dot.dtype) {
777 75 : case FretDotType::NORMAL: typeChar = u'O';
778 75 : break;
779 0 : case FretDotType::CROSS: typeChar = u'X';
780 0 : break;
781 0 : case FretDotType::SQUARE: typeChar = u'S';
782 0 : break;
783 0 : case FretDotType::TRIANGLE: typeChar = u'T';
784 0 : break;
785 0 : default: break;
786 : }
787 :
788 75 : dotDescriptions.push_back(String::number(actualFret) + u'-' + typeChar);
789 : }
790 :
791 75 : patternParts.push_back(u'[' + dotDescriptions.join(u",") + u']');
792 75 : } else {
793 182 : patternParts.push_back(u"-");
794 : }
795 257 : }
796 :
797 56 : String pattern = patternParts.join(u"");
798 :
799 56 : const BarreMap& _barres = barres();
800 0 : StringList barreParts;
801 63 : for (const auto& [fret, b] : _barres) {
802 7 : if (!b.exists()) {
803 0 : continue;
804 : }
805 :
806 7 : int adjustedFret = fret + offset;
807 7 : int start = b.startString;
808 7 : int end = (b.endString != -1) ? b.endString : diagramStrings - 1;
809 :
810 21 : barreParts.push_back(u'B' + String::number(adjustedFret)
811 28 : + u'[' + String::number(start)
812 28 : + u'-' + String::number(end) + u']');
813 : }
814 :
815 56 : if (!barreParts.empty()) {
816 7 : pattern += u';' + barreParts.join(u";");
817 : }
818 :
819 112 : return pattern;
820 56 : }
821 :
822 16 : std::vector<String> FretDiagram::harmoniesFromPattern(const String& pattern) const
823 : {
824 16 : if (s_diagramPatternToHarmoniesMap.empty()) {
825 0 : readHarmonyToDiagramFile(HARMONY_TO_DIAGRAM_FILE_PATH);
826 : }
827 16 : return muse::value(s_diagramPatternToHarmoniesMap, pattern);
828 : }
829 :
830 105 : std::vector<DiagramInfo> FretDiagram::patternsFromHarmony(const String& harmonyName)
831 : {
832 105 : if (s_harmonyToDiagramMap.empty()) {
833 0 : readHarmonyToDiagramFile(HARMONY_TO_DIAGRAM_FILE_PATH);
834 : }
835 :
836 105 : NoteSpellingType spellingType = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
837 105 : HarmonyMapKey key = createHarmonyMapKey(harmonyName, spellingType, score()->chordList());
838 :
839 105 : if (tpcIsValid(key.rootTpc)) {
840 105 : key.rootTpc = canonicalEnharmonicTpc(key.rootTpc);
841 : }
842 105 : if (tpcIsValid(key.bassTpc)) {
843 2 : key.bassTpc = canonicalEnharmonicTpc(key.bassTpc);
844 : }
845 :
846 210 : return muse::value(s_harmonyToDiagramMap, key);
847 : }
848 :
849 : //---------------------------------------------------------
850 : // clear
851 : //---------------------------------------------------------
852 :
853 70 : void FretDiagram::clear()
854 : {
855 70 : m_barres.clear();
856 70 : m_dots.clear();
857 70 : m_markers.clear();
858 70 : m_fretOffset = 0;
859 70 : }
860 :
861 3 : bool FretDiagram::isClear() const
862 : {
863 3 : return m_barres.empty() && m_dots.empty() && m_markers.empty();
864 : }
865 :
866 : //---------------------------------------------------------
867 : // undoFretClear
868 : //---------------------------------------------------------
869 :
870 1 : void FretDiagram::undoFretClear()
871 : {
872 2 : for (EngravingObject* e : linkList()) {
873 1 : FretDiagram* fd = toFretDiagram(e);
874 1 : fd->score()->undo(new FretClear(fd));
875 1 : }
876 1 : }
877 :
878 80 : int FretDiagram::numPos() const
879 : {
880 80 : return style().styleI(Sid::fretNumPos);
881 : }
882 :
883 : //---------------------------------------------------------
884 : // dot
885 : // take fret value of zero to mean all dots
886 : //---------------------------------------------------------
887 :
888 2036 : std::vector<FretItem::Dot> FretDiagram::dot(int s, int f /*= 0*/) const
889 : {
890 2036 : if (m_dots.find(s) != m_dots.end()) {
891 880 : if (f != 0) {
892 0 : for (auto const& d : m_dots.at(s)) {
893 0 : if (d.fret == f) {
894 0 : return std::vector<FretItem::Dot> { FretItem::Dot(d) };
895 : }
896 : }
897 : } else {
898 880 : return m_dots.at(s);
899 : }
900 : }
901 3468 : return std::vector<FretItem::Dot> { FretItem::Dot(0) };
902 : }
903 :
904 : //---------------------------------------------------------
905 : // marker
906 : //---------------------------------------------------------
907 :
908 954 : FretItem::Marker FretDiagram::marker(int s) const
909 : {
910 954 : if (m_markers.find(s) != m_markers.end()) {
911 393 : return m_markers.at(s);
912 : }
913 561 : return FretItem::Marker(FretMarkerType::NONE);
914 : }
915 :
916 : //---------------------------------------------------------
917 : // barre
918 : //---------------------------------------------------------
919 :
920 274 : FretItem::Barre FretDiagram::barre(int f) const
921 : {
922 274 : if (m_barres.find(f) != m_barres.end()) {
923 26 : return m_barres.at(f);
924 : }
925 248 : return FretItem::Barre(-1, -1);
926 : }
927 :
928 44 : Font FretDiagram::fretNumFont() const
929 : {
930 44 : const MStyle& st = style();
931 44 : Font f(st.styleSt(Sid::fretDiagramFretNumberFontFace), Font::Type::Text);
932 44 : f.setPointSizeF(st.styleD(Sid::fretDiagramFretNumberFontSize) * userMag());
933 44 : FontStyle fStyle = st.styleV(Sid::fretDiagramFretNumberFontStyle).value<FontStyle>();
934 44 : f.setBold(fStyle & FontStyle::Bold);
935 44 : f.setItalic(fStyle & FontStyle::Italic);
936 44 : f.setUnderline(fStyle & FontStyle::Underline);
937 44 : f.setStrike(fStyle & FontStyle::Strike);
938 44 : return f;
939 0 : }
940 :
941 0 : Font FretDiagram::fingeringFont() const
942 : {
943 0 : const MStyle& st = style();
944 0 : Font f(st.styleSt(Sid::fretDiagramFingeringFontFace), Font::Type::Text);
945 0 : f.setPointSizeF(st.styleD(Sid::fretDiagramFingeringFontSize) * userMag());
946 0 : FontStyle fStyle = st.styleV(Sid::fretDiagramFingeringFontStyle).value<FontStyle>();
947 0 : f.setBold(fStyle & FontStyle::Bold);
948 0 : f.setItalic(fStyle & FontStyle::Italic);
949 0 : f.setUnderline(fStyle & FontStyle::Underline);
950 0 : f.setStrike(fStyle & FontStyle::Strike);
951 0 : return f;
952 0 : }
953 :
954 41 : String FretDiagram::harmonyPlainText() const
955 : {
956 41 : return m_harmony ? m_harmony->plainText() : String();
957 : }
958 :
959 211 : String FretDiagram::harmonyDisplayText() const
960 : {
961 211 : return m_harmony ? m_harmony->displayText() : String();
962 : }
963 :
964 : //---------------------------------------------------------
965 : // setHarmony
966 : /// if this is being done by the user, use undoSetHarmony instead
967 : //---------------------------------------------------------
968 :
969 27 : void FretDiagram::setHarmony(String harmonyText)
970 : {
971 27 : if (!m_harmony) {
972 27 : Harmony* h = new Harmony(this);
973 27 : add(h);
974 : }
975 :
976 27 : m_harmony->setHarmony(harmonyText);
977 27 : m_harmony->setXmlText(m_harmony->harmonyName());
978 27 : triggerLayout();
979 27 : }
980 :
981 : //---------------------------------------------------------
982 : // add
983 : //---------------------------------------------------------
984 :
985 85 : void FretDiagram::add(EngravingItem* e)
986 : {
987 85 : e->setOwnershipParent(this);
988 85 : if (e->isHarmony()) {
989 85 : m_harmony = toHarmony(e);
990 :
991 85 : m_harmony->setTrack(track());
992 :
993 85 : if (m_harmony->harmonyName().empty()) {
994 28 : if (s_diagramPatternToHarmoniesMap.empty()) {
995 2 : readHarmonyToDiagramFile(HARMONY_TO_DIAGRAM_FILE_PATH);
996 : }
997 :
998 28 : String pattern = patternFromDiagram();
999 28 : if (!pattern.empty()) {
1000 28 : std::vector<String> matchedHarmonies = muse::value(s_diagramPatternToHarmoniesMap, pattern);
1001 28 : if (!matchedHarmonies.empty()) {
1002 1 : m_harmony->setHarmony(matchedHarmonies.front());
1003 : }
1004 28 : }
1005 28 : }
1006 :
1007 85 : e->added();
1008 : } else {
1009 0 : LOGW("FretDiagram: cannot add <%s>\n", e->typeName());
1010 : }
1011 85 : }
1012 :
1013 : //---------------------------------------------------------
1014 : // remove
1015 : //---------------------------------------------------------
1016 :
1017 0 : void FretDiagram::remove(EngravingItem* e)
1018 : {
1019 0 : if (e == m_harmony) {
1020 0 : m_harmony = nullptr;
1021 0 : e->removed();
1022 : } else {
1023 0 : LOGW("FretDiagram: cannot remove <%s>\n", e->typeName());
1024 : }
1025 0 : }
1026 :
1027 0 : RectF FretDiagram::drag(EditData& ed)
1028 : {
1029 0 : RectF result = EngravingItem::drag(ed);
1030 :
1031 0 : MoveElementAnchors::moveElementAnchorsOnDrag(this, ed);
1032 :
1033 0 : return result;
1034 : }
1035 :
1036 : //---------------------------------------------------------
1037 : // acceptDrop
1038 : //---------------------------------------------------------
1039 :
1040 0 : bool FretDiagram::acceptDrop(EditData& data) const
1041 : {
1042 0 : return data.dropElement->isHarmony();
1043 : }
1044 :
1045 : //---------------------------------------------------------
1046 : // drop
1047 : //---------------------------------------------------------
1048 :
1049 0 : EngravingItem* FretDiagram::drop(Transaction&, EditData& data)
1050 : {
1051 0 : EngravingItem* e = data.dropElement;
1052 0 : if (e->isHarmony()) {
1053 0 : Harmony* h = toHarmony(e);
1054 0 : h->setOwnershipParent(ownershipParent());
1055 0 : h->setTrack(track());
1056 0 : score()->undoAddElement(h);
1057 : } else {
1058 0 : LOGW("FretDiagram: cannot drop <%s>\n", e->typeName());
1059 0 : delete e;
1060 0 : e = 0;
1061 : }
1062 0 : return e;
1063 : }
1064 :
1065 : //---------------------------------------------------------
1066 : // scanElements
1067 : //---------------------------------------------------------
1068 :
1069 9 : void FretDiagram::scanElements(std::function<void(EngravingItem*)> func)
1070 : {
1071 9 : func(this);
1072 :
1073 : // don't display harmony in palette
1074 9 : if (m_harmony && !score()->isPaletteScore()) {
1075 2 : func(m_harmony);
1076 : }
1077 9 : }
1078 :
1079 : //---------------------------------------------------------
1080 : // getProperty
1081 : //---------------------------------------------------------
1082 :
1083 1 : void FretDiagram::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
1084 : {
1085 1 : if (id == Pid::EXCLUDE_VERTICAL_ALIGN) {
1086 0 : EngravingItem::undoChangeProperty(id, v, ps);
1087 :
1088 0 : bool val = v.toBool();
1089 0 : Harmony* h = harmony();
1090 0 : if (h && h->excludeVerticalAlign() != val) {
1091 0 : h->undoChangeProperty(Pid::EXCLUDE_VERTICAL_ALIGN, val, ps);
1092 : }
1093 0 : Segment* parentSeg = segment();
1094 0 : if (!parentSeg) {
1095 0 : return;
1096 : }
1097 :
1098 0 : for (EngravingItem* item : parentSeg->annotations()) {
1099 0 : if ((!item->isFretDiagram() && !item->isHarmony()) || item == this || track2staff(item->track()) != staffIdx()) {
1100 0 : continue;
1101 : }
1102 :
1103 0 : if (item->excludeVerticalAlign() != val) {
1104 0 : item->undoChangeProperty(Pid::EXCLUDE_VERTICAL_ALIGN, val, ps);
1105 : }
1106 : }
1107 0 : return;
1108 : }
1109 :
1110 1 : EngravingItem::undoChangeProperty(id, v, ps);
1111 : }
1112 :
1113 745 : PropertyValue FretDiagram::getProperty(Pid propertyId) const
1114 : {
1115 745 : switch (propertyId) {
1116 0 : case Pid::MAG:
1117 0 : return userMag();
1118 0 : case Pid::FRET_STRINGS:
1119 0 : return strings();
1120 0 : case Pid::FRET_FRETS:
1121 0 : return frets();
1122 0 : case Pid::FRET_NUT:
1123 0 : return showNut();
1124 74 : case Pid::FRET_OFFSET:
1125 74 : return fretOffset();
1126 0 : case Pid::ORIENTATION:
1127 0 : return m_orientation;
1128 0 : case Pid::FRET_SHOW_FINGERINGS:
1129 0 : return m_showFingering;
1130 67 : case Pid::FRET_FINGERING:
1131 67 : return m_fingering;
1132 604 : default:
1133 604 : return EngravingItem::getProperty(propertyId);
1134 : }
1135 : }
1136 :
1137 : //---------------------------------------------------------
1138 : // setProperty
1139 : //---------------------------------------------------------
1140 :
1141 1438 : bool FretDiagram::setProperty(Pid propertyId, const PropertyValue& v)
1142 : {
1143 1438 : switch (propertyId) {
1144 156 : case Pid::MAG:
1145 156 : setUserMag(v.toDouble());
1146 156 : break;
1147 156 : case Pid::FRET_STRINGS:
1148 156 : setStrings(v.toInt());
1149 156 : break;
1150 179 : case Pid::FRET_FRETS:
1151 179 : setFrets(v.toInt());
1152 179 : break;
1153 156 : case Pid::FRET_NUT:
1154 156 : setShowNut(v.toBool());
1155 156 : break;
1156 11 : case Pid::FRET_OFFSET:
1157 11 : setFretOffset(v.toInt());
1158 11 : break;
1159 156 : case Pid::ORIENTATION:
1160 156 : m_orientation = v.value<Orientation>();
1161 156 : break;
1162 156 : case Pid::FRET_SHOW_FINGERINGS:
1163 156 : setShowFingering(v.toBool());
1164 156 : break;
1165 0 : case Pid::FRET_FINGERING:
1166 0 : setFingering(v.value<std::vector<int> >());
1167 0 : break;
1168 0 : case Pid::EXCLUDE_VERTICAL_ALIGN: {
1169 0 : setExcludeVerticalAlign(v.toBool());
1170 0 : break;
1171 : }
1172 468 : default:
1173 468 : return EngravingItem::setProperty(propertyId, v);
1174 : }
1175 970 : triggerLayout();
1176 970 : return true;
1177 : }
1178 :
1179 : //---------------------------------------------------------
1180 : // propertyDefault
1181 : //---------------------------------------------------------
1182 :
1183 747 : PropertyValue FretDiagram::propertyDefault(Pid pid) const
1184 : {
1185 : // We shouldn't style the fret offset
1186 747 : if (pid == Pid::FRET_OFFSET) {
1187 67 : return PropertyValue(0);
1188 680 : } else if (pid == Pid::FRET_FINGERING) {
1189 67 : return std::vector<int>(m_strings, 0);
1190 : }
1191 :
1192 6080 : for (const StyledProperty& p : *styledProperties()) {
1193 5477 : if (p.pid == pid) {
1194 10 : if (propertyType(pid) == P_TYPE::ABSOLUTE) {
1195 10 : return style().styleAbsolute(p.sid);
1196 : }
1197 10 : return style().styleV(p.sid);
1198 : }
1199 : }
1200 603 : return EngravingItem::propertyDefault(pid);
1201 : }
1202 :
1203 155 : void FretDiagram::setTrack(track_idx_t val)
1204 : {
1205 155 : EngravingItem::setTrack(val);
1206 :
1207 155 : if (m_harmony) {
1208 72 : m_harmony->setTrack(val);
1209 : }
1210 155 : }
1211 :
1212 : //---------------------------------------------------------
1213 : // accessibleInfo
1214 : //---------------------------------------------------------
1215 :
1216 0 : String FretDiagram::accessibleInfo() const
1217 : {
1218 0 : String chordName;
1219 0 : if (m_harmony) {
1220 0 : chordName = muse::mtrc("engraving", "with chord symbol %1").arg(m_harmony->harmonyName());
1221 : } else {
1222 0 : chordName = muse::mtrc("engraving", "without chord symbol");
1223 : }
1224 0 : return String(u"%1 %2").arg(translatedTypeUserName(), chordName);
1225 0 : }
1226 :
1227 : //---------------------------------------------------------
1228 : // screenReaderInfo
1229 : //---------------------------------------------------------
1230 :
1231 0 : String FretDiagram::screenReaderInfo() const
1232 : {
1233 0 : String detailedInfo;
1234 0 : for (int i = 0; i < m_strings; i++) {
1235 0 : String stringIdent = muse::mtrc("engraving", "string %1").arg(i + 1);
1236 :
1237 0 : const FretItem::Marker& m = marker(i);
1238 0 : String markerName;
1239 0 : switch (m.mtype) {
1240 0 : case FretMarkerType::CIRCLE:
1241 0 : markerName = muse::mtrc("engraving", "circle marker");
1242 0 : break;
1243 0 : case FretMarkerType::CROSS:
1244 0 : markerName = muse::mtrc("engraving", "cross marker");
1245 0 : break;
1246 0 : case FretMarkerType::NONE:
1247 : default:
1248 0 : break;
1249 : }
1250 :
1251 0 : int dotsCount = 0;
1252 0 : std::vector<int> fretsWithDots;
1253 0 : for (auto const& d : dot(i)) {
1254 0 : if (!d.exists()) {
1255 0 : continue;
1256 : }
1257 0 : fretsWithDots.push_back(d.fret + m_fretOffset);
1258 0 : dotsCount += 1;
1259 : // TODO consider: do we need to announce what type of dot a dot is?
1260 : // i.e. triangle, square, normal dot. It's mostly just information
1261 : // that clutters the screenreader output and makes it harder to
1262 : // understand, so leaving it out for now.
1263 0 : }
1264 :
1265 0 : if (dotsCount == 0 && markerName.size() == 0) {
1266 0 : continue;
1267 : }
1268 :
1269 0 : String fretInfo;
1270 0 : if (dotsCount == 1) {
1271 0 : fretInfo = String::number(fretsWithDots.front());
1272 0 : } else if (dotsCount > 1) {
1273 0 : int max = int(fretsWithDots.size());
1274 0 : for (int j = 0; j < max; j++) {
1275 0 : if (j == max - 1) {
1276 0 : fretInfo = muse::mtrc("engraving", "%1 and %2").arg(fretInfo).arg(fretsWithDots[j]);
1277 : } else {
1278 0 : fretInfo = String(u"%1 %2").arg(fretInfo).arg(fretsWithDots[j]);
1279 : }
1280 : }
1281 : }
1282 :
1283 : //: Omit the "%n " for the singular translation (and the "(s)" too)
1284 0 : String dotsInfo = muse::mtrc("engraving", "%n dot(s) on fret(s) %1", "", dotsCount).arg(fretInfo);
1285 :
1286 0 : detailedInfo = String(u"%1 %2 %3 %4").arg(detailedInfo, stringIdent, markerName, dotsInfo);
1287 0 : }
1288 :
1289 0 : String barreInfo;
1290 0 : for (auto const& iter : m_barres) {
1291 0 : const FretItem::Barre& b = iter.second;
1292 0 : if (!b.exists()) {
1293 0 : continue;
1294 : }
1295 :
1296 0 : String fretInfo = muse::mtrc("engraving", "fret %1").arg(iter.first);
1297 :
1298 0 : String newBarreInfo;
1299 0 : if (b.startString == 0 && (b.endString == -1 || b.endString == m_strings - 1)) {
1300 0 : newBarreInfo = muse::mtrc("engraving", "barré %1").arg(fretInfo);
1301 : } else {
1302 0 : String startPart = muse::mtrc("engraving", "beginning string %1").arg(b.startString + 1);
1303 0 : String endPart;
1304 0 : if (b.endString != -1) {
1305 0 : endPart = muse::mtrc("engraving", "and ending string %1").arg(b.endString + 1);
1306 : }
1307 :
1308 0 : newBarreInfo = muse::mtrc("engraving", "partial barré %1 %2 %3").arg(fretInfo, startPart, endPart);
1309 0 : }
1310 :
1311 0 : barreInfo = String(u"%1 %2").arg(barreInfo, newBarreInfo);
1312 0 : }
1313 :
1314 0 : detailedInfo = String(u"%1 %2").arg(detailedInfo, barreInfo);
1315 :
1316 0 : if (detailedInfo.trimmed().size() == 0) {
1317 0 : detailedInfo = muse::mtrc("engraving", "no content");
1318 : }
1319 :
1320 0 : String chordName = m_harmony
1321 0 : ? muse::mtrc("engraving", "with chord symbol %1").arg(m_harmony->generateScreenReaderInfo())
1322 0 : : muse::mtrc("engraving", "without chord symbol");
1323 :
1324 0 : String basicInfo = String(u"%1 %2").arg(translatedTypeUserName(), chordName);
1325 :
1326 0 : String generalInfo = muse::mtrc("engraving", "%n string(s) total", "", m_strings);
1327 :
1328 0 : String res = String(u"%1 %2 %3").arg(basicInfo, generalInfo, detailedInfo);
1329 :
1330 0 : return res;
1331 0 : }
1332 :
1333 0 : void FretDiagram::setFingering(std::vector<int> v)
1334 : {
1335 0 : m_fingering = std::move(v);
1336 0 : }
1337 :
1338 22 : FretDiagram* FretDiagram::makeFromHarmonyOrFretDiagram(const EngravingItem* harmonyOrFretDiagram)
1339 : {
1340 22 : IF_ASSERT_FAILED(!harmonyOrFretDiagram || !harmonyOrFretDiagram->isHarmony() || !harmonyOrFretDiagram->isFretDiagram()) {
1341 0 : return nullptr;
1342 : }
1343 :
1344 22 : FretDiagram* fretDiagram = nullptr;
1345 :
1346 22 : if (harmonyOrFretDiagram->isHarmony() && !harmonyOrFretDiagram->ownershipParent()->isFretDiagram()) {
1347 0 : Harmony* harmony = toHarmony(harmonyOrFretDiagram)->clone();
1348 :
1349 0 : fretDiagram = Factory::createFretDiagram(harmonyOrFretDiagram->score()->dummy());
1350 :
1351 0 : fretDiagram->updateDiagram(harmony->plainText());
1352 :
1353 0 : harmony->resetProperty(Pid::OFFSET);
1354 0 : harmony->setProperty(Pid::ALIGN, Align(AlignH::HCENTER, AlignV::BASELINE));
1355 0 : harmony->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
1356 :
1357 0 : fretDiagram->add(harmony);
1358 22 : } else if (harmonyOrFretDiagram->isHarmony() && harmonyOrFretDiagram->ownershipParent()->isFretDiagram()) {
1359 0 : fretDiagram = toFretDiagram(harmonyOrFretDiagram->ownershipParent())->clone();
1360 22 : } else if (harmonyOrFretDiagram->isFretDiagram()) {
1361 22 : fretDiagram = toFretDiagram(harmonyOrFretDiagram)->clone();
1362 22 : fretDiagram->setOffset(PointF());
1363 22 : if (!fretDiagram->harmony()) {
1364 : //! generate from diagram and add harmony
1365 0 : fretDiagram->add(Factory::createHarmony(fretDiagram));
1366 : }
1367 : }
1368 :
1369 22 : return fretDiagram;
1370 : }
1371 :
1372 0 : bool FretDiagram::isInFretBox() const
1373 : {
1374 0 : EngravingObject* parent = ownershipParent();
1375 0 : return parent ? parent->isFBox() : false;
1376 : }
1377 :
1378 0 : bool FretDiagram::isCustom(const String& harmonyNameForCompare) const
1379 : {
1380 0 : if (harmonyNameForCompare.empty()) {
1381 0 : return false;
1382 : }
1383 :
1384 : static constexpr std::array props {
1385 : Pid::FRET_STRINGS,
1386 : Pid::FRET_FRETS,
1387 : Pid::FRET_NUT,
1388 : Pid::FRET_FINGERING
1389 : };
1390 :
1391 0 : for (const Pid& pid : props) {
1392 0 : if (pid == Pid::FRET_FINGERING) {
1393 0 : if (custom(Pid::FRET_SHOW_FINGERINGS)) {
1394 0 : return true;
1395 : }
1396 0 : } else if (custom(pid)) {
1397 0 : return true;
1398 : }
1399 : }
1400 :
1401 0 : if (s_harmonyToDiagramMap.empty()) {
1402 0 : readHarmonyToDiagramFile(HARMONY_TO_DIAGRAM_FILE_PATH);
1403 : }
1404 :
1405 0 : NoteSpellingType spellingType = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
1406 0 : HarmonyMapKey key = createHarmonyMapKey(harmonyNameForCompare, spellingType, score()->chordList());
1407 :
1408 0 : if (tpcIsValid(key.rootTpc)) {
1409 0 : key.rootTpc = canonicalEnharmonicTpc(key.rootTpc);
1410 : }
1411 0 : if (tpcIsValid(key.bassTpc)) {
1412 0 : key.bassTpc = canonicalEnharmonicTpc(key.bassTpc);
1413 : }
1414 :
1415 0 : std::vector<DiagramInfo> availableDiagrams = muse::value(s_harmonyToDiagramMap, key);
1416 0 : if (availableDiagrams.empty()) {
1417 0 : return true;
1418 : }
1419 :
1420 0 : String currentPattern = patternFromDiagram();
1421 :
1422 0 : for (const DiagramInfo& diagram : availableDiagrams) {
1423 0 : if (diagram.diagramPattern == currentPattern) {
1424 0 : return false;
1425 : }
1426 : }
1427 :
1428 0 : return true;
1429 0 : }
1430 :
1431 2 : void FretDiagram::readHarmonyToDiagramFile(const muse::io::path_t& filePath) const
1432 : {
1433 4 : TRACEFUNC;
1434 :
1435 2 : muse::io::File file(filePath);
1436 2 : if (!file.open()) {
1437 0 : LOGE() << file.errorString();
1438 0 : return;
1439 : }
1440 :
1441 2 : XmlReader reader(&file);
1442 :
1443 : std::unordered_map<String, rw::HarmonyToDiagramReader::FretDiagramInfo> harmonyToDiagramMap
1444 2 : = rw::HarmonyToDiagramReader::read(reader);
1445 :
1446 2 : const ChordList* chordList = score()->chordList();
1447 2 : const NoteSpellingType spellingType = NoteSpellingType::STANDARD;
1448 :
1449 6250 : for (auto& [key, value] : harmonyToDiagramMap) {
1450 6248 : HarmonyMapKey mapKey = createHarmonyMapKey(key, spellingType, chordList);
1451 :
1452 6248 : if (tpcIsValid(mapKey.rootTpc)) {
1453 6246 : mapKey.rootTpc = canonicalEnharmonicTpc(mapKey.rootTpc);
1454 : }
1455 6248 : if (tpcIsValid(mapKey.bassTpc)) {
1456 3512 : mapKey.bassTpc = canonicalEnharmonicTpc(mapKey.bassTpc);
1457 : }
1458 :
1459 12496 : s_harmonyToDiagramMap[mapKey].push_back({ key, value.xml, value.pattern });
1460 6248 : s_diagramPatternToHarmoniesMap[value.pattern].push_back(key);
1461 : }
1462 6250 : }
1463 :
1464 : //---------------------------------------------------------
1465 : // markerToChar
1466 : //---------------------------------------------------------
1467 :
1468 0 : Char FretItem::markerToChar(FretMarkerType t)
1469 : {
1470 0 : switch (t) {
1471 0 : case FretMarkerType::CIRCLE: return Char(u'O');
1472 0 : case FretMarkerType::CROSS: return Char(u'X');
1473 0 : case FretMarkerType::NONE:
1474 : default:
1475 0 : return Char();
1476 : }
1477 : }
1478 :
1479 : //---------------------------------------------------------
1480 : // markerTypeToName
1481 : //---------------------------------------------------------
1482 :
1483 : const std::vector<FretItem::MarkerTypeNameItem> FretItem::markerTypeNameMap = {
1484 : { FretMarkerType::CIRCLE, "circle" },
1485 : { FretMarkerType::CROSS, "cross" },
1486 : { FretMarkerType::NONE, "none" }
1487 : };
1488 :
1489 130 : String FretItem::markerTypeToName(FretMarkerType t)
1490 : {
1491 177 : for (auto i : FretItem::markerTypeNameMap) {
1492 177 : if (i.mtype == t) {
1493 130 : return String::fromAscii(i.name);
1494 : }
1495 : }
1496 :
1497 0 : ASSERT_X("Unrecognised FretMarkerType!");
1498 : return String(); // prevent compiler warnings
1499 : }
1500 :
1501 : //---------------------------------------------------------
1502 : // nameToMarkerType
1503 : //---------------------------------------------------------
1504 :
1505 271 : FretMarkerType FretItem::nameToMarkerType(String n)
1506 : {
1507 366 : for (auto i : FretItem::markerTypeNameMap) {
1508 366 : if (String::fromAscii(i.name) == n) {
1509 271 : return i.mtype;
1510 : }
1511 : }
1512 0 : LOGW("Unrecognised marker name!");
1513 0 : return FretMarkerType::NONE; // default
1514 : }
1515 :
1516 : //---------------------------------------------------------
1517 : // dotTypeToName
1518 : //---------------------------------------------------------
1519 :
1520 : const std::vector<FretItem::DotTypeNameItem> FretItem::dotTypeNameMap = {
1521 : { FretDotType::NORMAL, "normal" },
1522 : { FretDotType::CROSS, "cross" },
1523 : { FretDotType::SQUARE, "square" },
1524 : { FretDotType::TRIANGLE, "triangle" },
1525 : };
1526 :
1527 202 : String FretItem::dotTypeToName(FretDotType t)
1528 : {
1529 202 : for (auto i : FretItem::dotTypeNameMap) {
1530 202 : if (i.dtype == t) {
1531 202 : return String::fromAscii(i.name);
1532 : }
1533 : }
1534 :
1535 0 : ASSERT_X("Unrecognised FretDotType!");
1536 : return String(); // prevent compiler warnings
1537 : }
1538 :
1539 : //---------------------------------------------------------
1540 : // nameToDotType
1541 : //---------------------------------------------------------
1542 :
1543 333 : FretDotType FretItem::nameToDotType(String n)
1544 : {
1545 333 : for (auto i : FretItem::dotTypeNameMap) {
1546 333 : if (String::fromAscii(i.name) == n) {
1547 333 : return i.dtype;
1548 : }
1549 : }
1550 0 : LOGW("Unrecognised dot name!");
1551 0 : return FretDotType::NORMAL; // default
1552 : }
1553 : }
|