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 "harmony.h"
24 :
25 : #include "containers.h"
26 : #include "translation.h"
27 :
28 : #include "draw/fontmetrics.h"
29 :
30 : #include "iengravingconfiguration.h" // IWYU pragma: keep
31 :
32 : #include "../editing/transpose.h"
33 : #include "../editing/transaction/transaction.h"
34 : #include "../editing/transaction/undostack.h"
35 : #include "../rendering/iscorerenderer.h"
36 :
37 : #include "chordlist.h"
38 : #include "fret.h"
39 : #include "linkedobjects.h"
40 : #include "measure.h"
41 : #include "mscore.h"
42 : #include "part.h"
43 : #include "parenthesis.h"
44 : #include "pitchspelling.h"
45 : #include "repeatlist.h"
46 : #include "score.h"
47 : #include "segment.h"
48 : #include "staff.h"
49 : #include "textbase.h"
50 : #include "utils.h"
51 :
52 : #include "log.h"
53 :
54 : using namespace mu;
55 : using namespace muse::draw;
56 : using namespace mu::engraving;
57 :
58 : namespace mu::engraving {
59 : static const char* FALLBACK_SYMBOLTEXT_FONT = "Bravura Text";
60 :
61 : //---------------------------------------------------------
62 : // descr
63 : // look up id in chord list
64 : // return chord description if found, or null
65 : //---------------------------------------------------------
66 :
67 6141 : const ChordDescription* HarmonyInfo::descr() const
68 : {
69 6141 : if (!chordList()) {
70 0 : return nullptr;
71 : }
72 6141 : return chordList()->description(id());
73 : }
74 :
75 : //---------------------------------------------------------
76 : // descr
77 : // look up name in chord list
78 : // optionally look up by parsed chord as fallback
79 : // return chord description if found, or null
80 : //---------------------------------------------------------
81 :
82 853 : const ChordDescription* HarmonyInfo::descr(const String& name, const ParsedChord* pc) const
83 : {
84 853 : const ChordDescription* match = nullptr;
85 853 : if (!chordList()) {
86 0 : return nullptr;
87 : }
88 2828 : for (const auto& p : *chordList()) {
89 2592 : const ChordDescription& cd = p.second;
90 4567 : for (const String& s : cd.names) {
91 2592 : if (s == name) {
92 617 : return &cd;
93 : }
94 1975 : if (!pc) {
95 1494 : continue;
96 : }
97 481 : for (const ParsedChord& sParsed : cd.parsedChords) {
98 0 : if (sParsed == *pc) {
99 0 : match = &cd;
100 : }
101 : }
102 : }
103 : }
104 : // exact match failed, so fall back on parsed match if one was found
105 236 : return match;
106 : }
107 :
108 : //---------------------------------------------------------
109 : // getDescription
110 : // look up id in chord list
111 : // return chord description if found
112 : // if not found, and chord is parseable,
113 : // generate a new chord description
114 : // and add to chord list
115 : //---------------------------------------------------------
116 :
117 3630 : const ChordDescription* HarmonyInfo::getDescription()
118 : {
119 3630 : const ChordDescription* cd = descr();
120 3630 : if (cd && !cd->names.empty()) {
121 3621 : m_textName = cd->names.front();
122 9 : } else if (!m_textName.empty()) {
123 9 : cd = generateDescription();
124 9 : m_id = cd->id;
125 : }
126 3630 : return cd;
127 : }
128 :
129 : //---------------------------------------------------------
130 : // getDescription
131 : // same but lookup by name and optionally parsed chord
132 : //---------------------------------------------------------
133 :
134 782 : const ChordDescription* HarmonyInfo::getDescription(const String& name, const ParsedChord* pc)
135 : {
136 782 : const ChordDescription* cd = descr(name, pc);
137 782 : if (cd) {
138 578 : m_id = cd->id;
139 : } else {
140 204 : cd = generateDescription();
141 204 : m_id = cd->id;
142 : }
143 782 : return cd;
144 : }
145 :
146 : //---------------------------------------------------------
147 : // generateDescription
148 : // generate new chord description from _textName
149 : // add to chord list using private id
150 : //---------------------------------------------------------
151 :
152 262 : const ChordDescription* HarmonyInfo::generateDescription()
153 : {
154 262 : ChordDescription cd(m_textName);
155 262 : cd.complete(getParsedChord(), chordList());
156 : // remove parsed chord from description
157 : // so we will only match it literally in the future
158 262 : cd.parsedChords.clear();
159 262 : chordList()->insert({ cd.id, cd });
160 524 : return &chordList()->at(cd.id);
161 262 : }
162 :
163 882 : ParsedChord* HarmonyInfo::getParsedChord()
164 : {
165 882 : if (!m_parsedChord) {
166 608 : m_parsedChord = new ParsedChord();
167 608 : m_parsedChord->parse(m_textName, chordList(), false);
168 : }
169 882 : return m_parsedChord;
170 : }
171 :
172 0 : bool HarmonyInfo::hasModifiers() const
173 : {
174 0 : return m_parsedChord ? m_parsedChord->modifiers().size() > 0 : false;
175 : }
176 :
177 212 : String Harmony::displayText() const
178 : {
179 : // String representation of what the user sees on the score
180 : // Used in fretbox
181 212 : String name;
182 386 : for (const HarmonyRenderItem* segment : ldata()->renderItemList()) {
183 174 : if (const TextSegment* textSeg = dynamic_cast<const TextSegment*>(segment)) {
184 174 : String t = textSeg->text();
185 174 : t.replace(u"\uE87C", u"/");
186 174 : name += t;
187 174 : } else if (const ChordSymbolParen* parenSeg = dynamic_cast<const ChordSymbolParen*>(segment)) {
188 0 : name += parenSeg->parenItem->direction() == DirectionH::LEFT ? u"(" : u")";
189 : }
190 : }
191 :
192 212 : return name;
193 0 : }
194 :
195 : //---------------------------------------------------------
196 : // harmonyName
197 : //---------------------------------------------------------
198 :
199 986 : String Harmony::harmonyName() const
200 : {
201 986 : String name;
202 :
203 986 : if (leftParen()) {
204 1 : name = u"(";
205 : }
206 :
207 1948 : for (size_t i = 0; i < m_chords.size(); i++) {
208 962 : const HarmonyInfo* info = m_chords.at(i);
209 962 : HChord hc = info->descr() ? info->descr()->chord : HChord();
210 962 : String s, r, e, b;
211 :
212 962 : if (i != 0) {
213 4 : name += u"|";
214 : }
215 :
216 962 : if (m_harmonyType == HarmonyType::STANDARD && tpcIsValid(info->rootTpc())) {
217 913 : NoteSpellingType spelling = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
218 913 : r = tpc2name(info->rootTpc(), spelling, m_rootCase);
219 49 : } else if (m_harmonyType == HarmonyType::NASHVILLE && tpcIsValid(info->rootTpc())) {
220 24 : const Staff* st = staff();
221 24 : Key key = st ? st->key(tick()) : Key::INVALID;
222 24 : r = tpc2Function(info->rootTpc(), key);
223 : }
224 :
225 962 : if (!info->textName().empty()) {
226 594 : e = info->textName();
227 594 : if (m_harmonyType != HarmonyType::ROMAN) {
228 575 : e.remove(u'=');
229 : }
230 368 : } else if (!m_degreeList.empty()) {
231 0 : hc.add(m_degreeList);
232 : // try to find the chord in chordList
233 0 : const ChordDescription* newExtension = nullptr;
234 0 : const ChordList* cl = score()->chordList();
235 0 : for (const auto& p : *cl) {
236 0 : const ChordDescription& cd = p.second;
237 0 : if (cd.chord == hc && !cd.names.empty()) {
238 0 : newExtension = &cd;
239 0 : break;
240 : }
241 : }
242 : // now determine the chord name
243 0 : if (newExtension) {
244 0 : e = newExtension->names.front();
245 : } else {
246 : // not in table, fallback to using HChord.name()
247 0 : r = hc.name(info->rootTpc());
248 0 : e = u"";
249 : }
250 : }
251 :
252 962 : if (tpcIsValid(info->bassTpc())) {
253 13 : NoteSpellingType spelling = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
254 13 : b = u"/" + tpc2name(info->bassTpc(), spelling, m_bassCase);
255 : }
256 :
257 962 : s += r + e + b;
258 :
259 962 : name += s;
260 962 : }
261 :
262 986 : if (rightParen()) {
263 1 : name += u")";
264 : }
265 :
266 986 : return name;
267 0 : }
268 :
269 492 : bool Harmony::isRealizable() const
270 : {
271 492 : if (m_chords.empty()) {
272 0 : return false;
273 : }
274 984 : for (const HarmonyInfo* info : m_chords) {
275 492 : if (!tpcIsValid(info->rootTpc())) {
276 0 : return false;
277 : }
278 : }
279 492 : return true;
280 : }
281 :
282 7 : int Harmony::bassTpc() const
283 : {
284 7 : if (m_chords.empty()) {
285 0 : return Tpc::TPC_INVALID;
286 : }
287 7 : return m_chords.front()->bassTpc();
288 : }
289 :
290 132 : int Harmony::rootTpc() const
291 : {
292 132 : if (m_chords.empty()) {
293 0 : return Tpc::TPC_INVALID;
294 : }
295 132 : return m_chords.front()->rootTpc();
296 : }
297 :
298 0 : bool Harmony::isInFretBox() const
299 : {
300 0 : EngravingObject* parent = ownershipParent();
301 0 : if (!parent) {
302 0 : return false;
303 : }
304 :
305 0 : if (parent->isFretDiagram()) {
306 0 : return toFretDiagram(parent)->isInFretBox();
307 : }
308 :
309 0 : return parent->isFBox();
310 : }
311 :
312 : //---------------------------------------------------------
313 : // chordSymbolStyle
314 : //---------------------------------------------------------
315 :
316 : const ElementStyle chordSymbolStyle {
317 : { Sid::harmonyPlacement, Pid::PLACEMENT },
318 : { Sid::minHarmonyDistance, Pid::MIN_DISTANCE },
319 : { Sid::chordBassNoteScale, Pid::HARMONY_BASS_SCALE },
320 : { Sid::harmonyVoiceLiteral, Pid::HARMONY_VOICE_LITERAL },
321 : { Sid::harmonyVoicing, Pid::HARMONY_VOICING },
322 : { Sid::harmonyDuration, Pid::HARMONY_DURATION },
323 : };
324 :
325 : //---------------------------------------------------------
326 : // Harmony
327 : //---------------------------------------------------------
328 :
329 1071 : Harmony::Harmony(EngravingObject* parent)
330 1071 : : TextBase(ElementType::HARMONY, parent, TextStyleType::HARMONY_A, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
331 : {
332 1071 : assert(!parent || parent->isSegment() || parent->isFretDiagram() || parent->isType(ElementType::DUMMY));
333 :
334 1071 : m_rootCase = NoteCaseType::CAPITAL;
335 1071 : m_bassCase = NoteCaseType::CAPITAL;
336 1071 : m_harmonyType = HarmonyType::STANDARD;
337 1071 : m_play = true;
338 1071 : m_realizedHarmony = RealizedHarmony(this);
339 1071 : initElementStyle(&chordSymbolStyle);
340 1071 : }
341 :
342 66 : Harmony::Harmony(const Harmony& h)
343 66 : : TextBase(h)
344 : {
345 66 : m_rootCase = h.m_rootCase;
346 66 : m_bassCase = h.m_bassCase;
347 66 : m_bassScale = h.m_bassScale;
348 66 : m_degreeList = h.m_degreeList;
349 66 : m_harmonyType = h.m_harmonyType;
350 66 : m_play = h.m_play;
351 66 : m_realizedHarmony = h.m_realizedHarmony;
352 66 : m_realizedHarmony.setHarmony(this);
353 132 : for (const HarmonyInfo* hi : h.m_chords) {
354 66 : HarmonyInfo* newInfo = new HarmonyInfo(*hi);
355 66 : m_chords.push_back(newInfo);
356 : }
357 :
358 66 : m_fontFamily = h.m_fontFamily;
359 66 : m_fontStyle = h.m_fontStyle;
360 66 : m_fontSize = h.m_fontSize;
361 66 : }
362 :
363 : //---------------------------------------------------------
364 : // ~Harmony
365 : //---------------------------------------------------------
366 :
367 2176 : Harmony::~Harmony()
368 : {
369 4466 : for (const HarmonyRenderItem* renderItem : mutldata()->renderItemList()) {
370 3370 : delete renderItem;
371 : }
372 2199 : for (const HarmonyInfo* info : m_chords) {
373 1103 : delete info;
374 : }
375 2176 : }
376 :
377 0 : int Harmony::id() const
378 : {
379 0 : if (m_chords.empty()) {
380 0 : return -1;
381 : }
382 0 : return m_chords.front()->id();
383 : }
384 :
385 : //---------------------------------------------------------
386 : // getParentSeg
387 : /// gets the parent segment of this harmony
388 : //---------------------------------------------------------
389 :
390 1713 : Segment* Harmony::getParentSeg() const
391 : {
392 1713 : Segment* seg = nullptr;
393 1713 : if (!ownershipParent()) {
394 0 : return nullptr;
395 : }
396 :
397 1713 : if (ownershipParent()->isFretDiagram()) {
398 : // When this harmony is the child of a fret diagram, we need to go up twice
399 : // to get to the parent seg.
400 34 : seg = toFretDiagram(ownershipParent())->segment();
401 : } else {
402 1679 : seg = toSegment(ownershipParent());
403 : }
404 1713 : return seg;
405 : }
406 :
407 227 : FretDiagram* Harmony::getParentFretDiagram() const
408 : {
409 227 : if (ownershipParent() && ownershipParent()->isFretDiagram()) {
410 223 : return toFretDiagram(ownershipParent());
411 : }
412 :
413 4 : return nullptr;
414 : }
415 :
416 708 : void Harmony::afterRead()
417 : {
418 : // TODO: now that we can render arbitrary chords,
419 : // we could try to construct a full representation from a degree list.
420 : // These will typically only exist for chords imported from MusicXML prior to MuseScore 2.0
421 : // or constructed in the Chord Symbol Properties dialog.
422 :
423 1419 : for (HarmonyInfo* info : m_chords) {
424 711 : if (tpcIsValid(info->rootTpc())) {
425 703 : if (info->id() > 0) {
426 : // positive id will happen only for scores that were created with explicit chord lists
427 : // lookup id in chord list and generate new description if necessary
428 0 : info->getDescription();
429 : } else {
430 : // default case: look up by name
431 : // description will be found for any chord already read in this score
432 : // and we will generate a new one if necessary
433 703 : info->getDescription(info->textName());
434 : }
435 8 : } else if (info->textName().empty()) {
436 : // unrecognized chords prior to 2.0 were stored as text with markup
437 : // we need to strip away the markup
438 : // this removes any user-applied formatting,
439 : // but we no longer support user-applied formatting for chord symbols anyhow
440 : // with any luck, the resulting text will be parseable now, so give it a shot
441 0 : createBlocks();
442 0 : String s = plainText();
443 0 : if (!s.isEmpty()) {
444 0 : setHarmony(s);
445 0 : return;
446 : }
447 : // empty text could also indicate a root-less slash chord ("/E")
448 : // we'll fall through and render it normally
449 0 : }
450 : }
451 :
452 : // render chord from description (or _textName)
453 708 : setPlainText(harmonyName());
454 : }
455 :
456 0 : bool Harmony::hasModifiers() const
457 : {
458 0 : for (const HarmonyInfo* info : m_chords) {
459 0 : if (info->hasModifiers()) {
460 0 : return true;
461 : }
462 : }
463 :
464 0 : return false;
465 : }
466 :
467 : //---------------------------------------------------------
468 : // parseHarmony
469 : // determine root and bass tpc & case
470 : // compare body of chordname against chord list
471 : // return true if chord is recognized
472 : //---------------------------------------------------------
473 :
474 87 : const std::vector<const ChordDescription*> Harmony::parseHarmony(const String& ss, bool syntaxOnly)
475 : {
476 : // pre-process for parentheses
477 87 : String s = ss.simplified();
478 87 : if (s.startsWith('(')) {
479 0 : setParenthesesMode(rightParen() ? ParenthesesMode::BOTH : ParenthesesMode::LEFT, true, false);
480 0 : s.remove(0, 1);
481 : } else {
482 87 : setParenthesesMode(rightParen() ? ParenthesesMode::RIGHT : ParenthesesMode::NONE, true, false);
483 : }
484 :
485 87 : if (s.endsWith(')') && s.count('(') < s.count(')')) {
486 0 : setParenthesesMode(leftParen() ? ParenthesesMode::BOTH : ParenthesesMode::RIGHT, true, false);
487 0 : s.remove(s.size() - 1, 1);
488 : } else {
489 87 : setParenthesesMode(leftParen() ? ParenthesesMode::LEFT : ParenthesesMode::NONE, true, false);
490 : }
491 :
492 87 : if (parenthesesMode() == ParenthesesMode::BOTH) {
493 0 : s = s.simplified(); // in case of spaces inside parentheses
494 : }
495 :
496 87 : muse::DeleteAll(m_chords);
497 87 : m_chords.clear();
498 :
499 87 : std::vector<const ChordDescription*> descriptions;
500 87 : if (s.isEmpty()) {
501 0 : return descriptions;
502 : }
503 :
504 87 : StringList chords = s.split('|');
505 :
506 175 : for (const String& subChord : chords) {
507 88 : if (subChord.empty()) {
508 0 : continue;
509 : }
510 88 : HarmonyInfo* info = new HarmonyInfo(score());
511 88 : const ChordDescription* cd = parseSingleHarmony(subChord, info, syntaxOnly);
512 88 : descriptions.push_back(cd);
513 88 : m_chords.push_back(info);
514 : }
515 :
516 87 : return descriptions;
517 87 : }
518 :
519 88 : const ChordDescription* Harmony::parseSingleHarmony(const String& ss, HarmonyInfo* info, bool syntaxOnly)
520 : {
521 88 : String s = ss.simplified();
522 :
523 88 : if (m_harmonyType == HarmonyType::ROMAN) {
524 16 : info->setTextName(s);
525 16 : info->setRootTpc(Tpc::TPC_INVALID);
526 16 : info->setBassTpc(Tpc::TPC_INVALID);
527 16 : return nullptr;
528 : }
529 :
530 72 : bool useLiteral = false;
531 72 : if (ss.endsWith(' ')) {
532 0 : useLiteral = true;
533 : }
534 :
535 : // pre-process for lower case minor chords
536 : bool preferMinor;
537 72 : if (style().styleB(Sid::lowerCaseMinorChords) && s.at(0).isLower()) {
538 0 : preferMinor = true;
539 : } else {
540 72 : preferMinor = false;
541 : }
542 :
543 72 : size_t idx = 0;
544 72 : const Staff* st = staff();
545 72 : Key key = st ? st->key(tick()) : Key::INVALID;
546 72 : NoteSpellingType spelling = style().styleV(Sid::chordSymbolSpelling).value<NoteSpellingType>();
547 :
548 72 : int r = Tpc::TPC_INVALID;
549 72 : if (m_harmonyType == HarmonyType::STANDARD) {
550 72 : r = convertNote(s, spelling, m_rootCase, idx);
551 0 : } else if (m_harmonyType == HarmonyType::NASHVILLE) {
552 0 : r = function2Tpc(s, key, idx);
553 : }
554 72 : if (!tpcIsValid(r)) {
555 1 : if (s.at(0) == '/') {
556 0 : idx = 0;
557 : } else {
558 1 : LOGD("failed <%s>", muPrintable(ss));
559 1 : info->setTextName(s);
560 1 : return 0;
561 : }
562 : }
563 71 : info->setRootTpc(r);
564 71 : info->setBassTpc(Tpc::TPC_INVALID);
565 71 : size_t slash = s.lastIndexOf(u'/');
566 71 : if (slash != muse::nidx) {
567 8 : String bs = s.mid(slash + 1).simplified();
568 8 : s = s.mid(idx, slash - idx).simplified();
569 8 : size_t idx2 = 0;
570 8 : if (m_harmonyType == HarmonyType::STANDARD) {
571 8 : info->setBassTpc(convertNote(bs, spelling, m_bassCase, idx2));
572 0 : } else if (m_harmonyType == HarmonyType::NASHVILLE) {
573 0 : info->setBassTpc(function2Tpc(bs, key, idx2));
574 : }
575 :
576 8 : if (idx2 != bs.size()) {
577 0 : info->setBassTpc(Tpc::TPC_INVALID);
578 : }
579 8 : if (!tpcIsValid(info->bassTpc())) {
580 : // if what follows after slash is not (just) a TPC
581 : // then reassemble chord and try to parse with the slash
582 0 : s = s + u"/" + bs;
583 : }
584 8 : } else {
585 63 : s = s.mid(idx); // don't simplify; keep leading space before extension if present
586 : }
587 :
588 71 : const ChordList* cl = score()->chordList();
589 71 : const ChordDescription* cd = 0;
590 71 : if (useLiteral) {
591 0 : cd = info->descr(s);
592 : } else {
593 71 : ParsedChord* pc = new ParsedChord();
594 71 : pc->parse(s, cl, syntaxOnly, preferMinor);
595 : // parser prepends "=" to name of implied minor chords
596 : // use this here as well
597 71 : if (preferMinor) {
598 0 : s = pc->name();
599 : }
600 : // look up to see if we already have a descriptor (chord has been used before)
601 71 : cd = info->descr(s, pc);
602 71 : info->setParsedChord(pc);
603 : }
604 71 : if (cd) {
605 : // descriptor found; use its information
606 39 : info->setId(cd->id);
607 39 : if (!cd->names.empty()) {
608 39 : info->setTextName(cd->names.front());
609 : }
610 : } else {
611 : // no descriptor yet; just set textname
612 : // we will generate descriptor later if necessary (when we are done editing this chord)
613 32 : info->setTextName(s);
614 : }
615 71 : return cd;
616 88 : }
617 :
618 3630 : NoteCaseType Harmony::rootRenderCase(HarmonyInfo* info) const
619 : {
620 : // case
621 : // always use case as typed if automatic capitalization is off
622 3630 : NoteCaseType noteCase = m_rootCase;
623 3630 : if (!style().styleB(Sid::automaticCapitalization)) {
624 0 : return noteCase;
625 : }
626 :
627 : // set default
628 3630 : if (style().styleB(Sid::allCapsNoteNames)) {
629 0 : noteCase = NoteCaseType::UPPER;
630 : } else {
631 3630 : noteCase = NoteCaseType::CAPITAL;
632 : }
633 :
634 : // override for minor chords
635 3630 : if (style().styleB(Sid::lowerCaseMinorChords)) {
636 0 : const ChordDescription* cd = info->descr();
637 0 : String quality;
638 0 : if (cd) {
639 : // use chord description if possible
640 : // this is the usual case
641 0 : quality = cd->quality();
642 0 : } else if (info->getParsedChord()) {
643 : // this happens on load of new chord list
644 : // for chord symbols that were added/edited since the score was loaded
645 : // or read aloud with screenreader
646 : // parsed form is usable even if out of date with respect to chord list
647 0 : quality = info->getParsedChord()->quality();
648 : } else {
649 : // this happens on load of new chord list
650 : // for chord symbols that have not been edited since the score was loaded
651 : // we need to parse this chord for now to determine quality
652 : // but don't keep the parsed form around as we're not ready for it yet
653 0 : quality = info->getParsedChord()->quality();
654 0 : delete info->parsedChord();
655 0 : info->setParsedChord(nullptr);
656 : }
657 0 : if (quality == "minor" || quality == "diminished" || quality == "half-diminished") {
658 0 : noteCase = NoteCaseType::LOWER;
659 : }
660 0 : }
661 :
662 3630 : return noteCase;
663 : }
664 :
665 3630 : NoteCaseType Harmony::bassRenderCase() const
666 : {
667 : // case
668 : // always use case as typed if automatic capitalization is off
669 3630 : NoteCaseType noteCase = m_bassCase;
670 3630 : if (!style().styleB(Sid::automaticCapitalization)) {
671 0 : return noteCase;
672 : }
673 :
674 : // set default
675 3630 : if (style().styleB(Sid::allCapsNoteNames)) {
676 0 : noteCase = NoteCaseType::UPPER;
677 : } else {
678 3630 : noteCase = NoteCaseType::CAPITAL;
679 : }
680 :
681 : // override for bass note
682 3630 : if (style().styleB(Sid::lowerCaseBassNotes)) {
683 0 : noteCase = NoteCaseType::LOWER;
684 : }
685 :
686 3630 : return noteCase;
687 : }
688 :
689 : //---------------------------------------------------------
690 : // startEdit
691 : //---------------------------------------------------------
692 :
693 0 : void Harmony::startEdit(EditData& ed)
694 : {
695 0 : if (!ldata()->renderItemList().empty()) {
696 : // convert chord symbol to plain text
697 0 : setPlainText(harmonyName());
698 : // clear rendering
699 0 : for (const HarmonyRenderItem* renderItem : mutldata()->renderItemList()) {
700 0 : delete renderItem;
701 : }
702 0 : mutldata()->renderItemList.mut_value().clear();
703 : }
704 :
705 0 : if (leftParen()) {
706 0 : leftParen()->mutldata()->reset();
707 : }
708 0 : if (rightParen()) {
709 0 : rightParen()->mutldata()->reset();
710 : }
711 :
712 : // layout as text, without position reset
713 0 : renderer()->layoutText1(this, true);
714 0 : triggerLayout();
715 :
716 0 : TextBase::startEdit(ed);
717 0 : }
718 :
719 0 : bool Harmony::isEditAllowed(EditData& ed) const
720 : {
721 0 : if (isTextNavigationKey(ed.key, ed.modifiers)) {
722 0 : return false;
723 : }
724 :
725 0 : if (ed.key == Key_Semicolon || ed.key == Key_Colon) {
726 0 : return false;
727 : }
728 :
729 0 : if ((ed.key == Key_Left || ed.key == Key_Right) && (ed.modifiers & ControlModifier)) {
730 0 : return false;
731 : }
732 :
733 0 : if (ed.key == Key_Return || ed.key == Key_Enter) {
734 : // This "edit" is actually handled in NotationInteraction::editElement
735 0 : return true;
736 : }
737 :
738 0 : return TextBase::isEditAllowed(ed);
739 : }
740 :
741 : //---------------------------------------------------------
742 : // edit
743 : //---------------------------------------------------------
744 :
745 0 : bool Harmony::edit(EditData& ed)
746 : {
747 0 : if (!isEditAllowed(ed)) {
748 0 : return false;
749 : }
750 :
751 0 : bool rv = TextBase::edit(ed);
752 :
753 : // layout as text, without position reset
754 0 : renderer()->layoutText1(this, true);
755 0 : triggerLayout();
756 :
757 : // check spelling
758 0 : String str = xmlText();
759 :
760 0 : std::vector<const ChordDescription*> descriptions = parseHarmony(str, true);
761 0 : bool descriptionsValid = true;
762 0 : for (const ChordDescription* cd : descriptions) {
763 0 : if (!cd) {
764 0 : descriptionsValid = false;
765 : }
766 : }
767 :
768 0 : bool tpcsValid = true;
769 0 : for (const HarmonyInfo* info : m_chords) {
770 0 : tpcsValid &= tpcIsValid(info->rootTpc());
771 : }
772 :
773 0 : m_isMisspelled = !str.isEmpty()
774 0 : && !descriptionsValid
775 0 : && !tpcsValid
776 0 : && m_harmonyType == HarmonyType::STANDARD;
777 :
778 0 : if (m_isMisspelled) {
779 0 : LOGD("bad spell");
780 : }
781 :
782 0 : return rv;
783 0 : }
784 :
785 : //---------------------------------------------------------
786 : // endEdit
787 : //---------------------------------------------------------
788 :
789 0 : void Harmony::endEdit(EditData& ed)
790 : {
791 : // get plain text
792 0 : String s = plainText();
793 :
794 : // if user explicitly added symbols to the text,
795 : // convert them back to their respective replacement texts
796 0 : if (harmonyType() != HarmonyType::ROMAN) {
797 0 : s.replace(u"\u1d12b", u"bb"); // double-flat
798 0 : s.replace(u"\u266d", u"b"); // flat
799 0 : s.replace(u"\ue260", u"b"); // flat
800 : // do not replace natural sign
801 : // (right now adding the symbol explicitly is the only way to force a natural sign to appear at all)
802 : //s.replace("\u266e", "n"); // natural, if one day we support that too
803 : //s.replace("\ue261", "n"); // natural, if one day we support that too
804 0 : s.replace(u"\u266f", u"#"); // sharp
805 0 : s.replace(u"\ue262", u"#"); // sharp
806 0 : s.replace(u"\u1d12a", u"x"); // double-sharp
807 0 : s.replace(u"\u0394", u"^"); // Δ
808 0 : s.replace(u"\u00d0", u"o"); // °
809 0 : s.replace(u"\u00f8", u"0"); // ø
810 0 : s.replace(u"\u00d8", u"0"); // Ø
811 : } else {
812 0 : s.replace(u"\ue260", u"\u266d"); // flat
813 0 : s.replace(u"\ue261", u"\u266e"); // natural
814 0 : s.replace(u"\ue262", u"\u266f"); // sharp
815 : }
816 :
817 : //play chord on edit and set dirty
818 0 : score()->setPlayChord(true);
819 :
820 0 : setHarmony(s);
821 0 : setPlainText(harmonyName());
822 :
823 : // disable spell check
824 0 : m_isMisspelled = false;
825 :
826 0 : const bool textEdited = textWasEdited(ed);
827 :
828 0 : TextBase::endEdit(ed);
829 :
830 0 : if (!links() || !textEdited) {
831 0 : return;
832 : }
833 :
834 0 : const bool thisConcertPitch = style().styleB(Sid::concertPitch);
835 0 : const Interval thisInterval = thisConcertPitch ? Interval(0, 0) : staff()->transpose(tick());
836 :
837 0 : for (EngravingObject* e : *links()) {
838 0 : if (e == this) {
839 0 : continue;
840 : }
841 0 : Harmony* h = toHarmony(e);
842 : // transpose if necessary
843 : // at this point chord will already have been rendered in same key as original
844 : // (as a result of TextBase::endEdit() calling setText() for linked elements)
845 : // we may now need to change the TPC's and the text, and re-render
846 0 : const bool linkedConcertPitch = h->style().styleB(Sid::concertPitch);
847 0 : if (thisConcertPitch == linkedConcertPitch) {
848 0 : continue;
849 : }
850 :
851 0 : Score* linkedScore = h->score();
852 0 : const Staff* linkedStaff = h->staff();
853 0 : const Interval linkedInterval = linkedConcertPitch ? Interval(0, 0) : linkedStaff->transpose(h->tick());
854 0 : const Interval transposeDiff(thisInterval.diatonic - linkedInterval.diatonic, thisInterval.chromatic - linkedInterval.chromatic);
855 0 : if (transposeDiff.isZero()) {
856 0 : continue;
857 : }
858 :
859 0 : UndoStack* undoStack = linkedScore->undoStack();
860 0 : const size_t undoIdx = undoStack->currentIndex();
861 :
862 0 : score()->transactionManager()->transaction(TranslatableString("undoableAction", "Transpose harmony"), [&](auto& tx) {
863 0 : Transpose::undoTransposeHarmony(tx, h, transposeDiff);
864 0 : });
865 :
866 : // Merge with end of text editing
867 0 : if (undoIdx != undoStack->currentIndex() && undoStack->currentIndex() >= 2) {
868 0 : const size_t penultimateCmdIdx = undoStack->currentIndex() - 2;
869 0 : undoStack->mergeTransactions(penultimateCmdIdx);
870 : }
871 : }
872 0 : }
873 :
874 0 : bool Harmony::isPlayable() const
875 : {
876 0 : return !isInFretBox();
877 : }
878 :
879 : //---------------------------------------------------------
880 : // setHarmony
881 : //---------------------------------------------------------
882 :
883 87 : void Harmony::setHarmony(const String& s)
884 : {
885 87 : m_realizedHarmony.setDirty(true);
886 :
887 87 : std::vector<const ChordDescription*> descriptions = parseHarmony(s);
888 175 : for (size_t i = 0; i < m_chords.size(); i++) {
889 88 : HarmonyInfo* info = m_chords.at(i);
890 88 : const ChordDescription* cd = i < descriptions.size() ? descriptions.at(i) : nullptr;
891 :
892 88 : if (!cd && info->getParsedChord()->parseable()) {
893 : // our first time encountering this chord
894 : // generate a descriptor and use it
895 49 : cd = info->generateDescription();
896 49 : info->setId(cd->id);
897 : }
898 88 : if (!cd) {
899 : // unparseable chord or roman numeral, render as plain text
900 0 : info->setRootTpc(Tpc::TPC_INVALID);
901 0 : info->setBassTpc(Tpc::TPC_INVALID);
902 0 : info->setId(-1);
903 : }
904 : }
905 87 : }
906 :
907 : //---------------------------------------------------------
908 : // baseLine
909 : //---------------------------------------------------------
910 :
911 3556 : double Harmony::baseLine() const
912 : {
913 3556 : if (ldata()->renderItemList().empty() || !ldata()->baseline.has_value()) {
914 0 : return TextBase::baseLine();
915 : }
916 :
917 3556 : return ldata()->baseline.value();
918 : }
919 :
920 : //---------------------------------------------------------
921 : // text
922 : //---------------------------------------------------------
923 :
924 10 : String HDegree::text() const
925 : {
926 10 : if (m_type == HDegreeType::UNDEF) {
927 0 : return String();
928 : }
929 10 : String degree;
930 10 : switch (m_type) {
931 0 : case HDegreeType::UNDEF: break;
932 8 : case HDegreeType::ADD: degree = u"add";
933 8 : break;
934 1 : case HDegreeType::ALTER: degree = u"alt";
935 1 : break;
936 1 : case HDegreeType::SUBTRACT: degree = u"sub";
937 1 : break;
938 : }
939 :
940 10 : switch (m_alter) {
941 1 : case -1: degree += u"b";
942 1 : break;
943 1 : case 1: degree += u"#";
944 1 : break;
945 8 : default: break;
946 : }
947 20 : String s = String::number(m_value);
948 20 : String ss = degree + s;
949 10 : return ss;
950 10 : }
951 :
952 : //---------------------------------------------------------
953 : // findInSeg
954 : /// find a Harmony in a given segment on the same track as this harmony.
955 : ///
956 : /// returns 0 if there is none
957 : //---------------------------------------------------------
958 :
959 1173 : Harmony* Harmony::findInSeg(Segment* seg) const
960 : {
961 : // Find harmony as parent of fret diagram on same track
962 1173 : EngravingItem* fde = seg->findAnnotation(ElementType::FRET_DIAGRAM, track(), track());
963 1173 : if (fde) {
964 0 : FretDiagram* fd = toFretDiagram(fde);
965 0 : if (fd->harmony()) {
966 0 : return toHarmony(fd->harmony());
967 : }
968 : }
969 :
970 : // Find harmony on same track
971 1173 : EngravingItem* e = seg->findAnnotation(ElementType::HARMONY, track(), track());
972 1173 : if (e) {
973 481 : return toHarmony(e);
974 : }
975 692 : return nullptr;
976 : }
977 :
978 : //---------------------------------------------------------
979 : // findNext
980 : /// find the next Harmony in the score
981 : ///
982 : /// returns 0 if there is none
983 : //---------------------------------------------------------
984 :
985 492 : Harmony* Harmony::findNext() const
986 : {
987 492 : const Segment* segment = getParentSeg();
988 492 : Segment* cur = segment ? segment->next1() : nullptr;
989 1184 : while (cur) {
990 1173 : Harmony* h = findInSeg(cur);
991 1173 : if (h) {
992 481 : return h;
993 : }
994 692 : cur = cur->next1();
995 : }
996 11 : return nullptr;
997 : }
998 :
999 : //---------------------------------------------------------
1000 : // findPrev
1001 : /// find the previous Harmony in the score
1002 : ///
1003 : /// returns 0 if there is none
1004 : //---------------------------------------------------------
1005 :
1006 0 : Harmony* Harmony::findPrev() const
1007 : {
1008 0 : const Segment* segment = getParentSeg();
1009 0 : Segment* cur = segment ? segment->prev1() : nullptr;
1010 0 : while (cur) {
1011 0 : Harmony* h = findInSeg(cur);
1012 0 : if (h) {
1013 0 : return h;
1014 : }
1015 0 : cur = cur->prev1();
1016 : }
1017 0 : return nullptr;
1018 : }
1019 :
1020 : //---------------------------------------------------------
1021 : // ticksTillNext
1022 : /// finds ticks until the next chord symbol or end of score
1023 : ///
1024 : /// utick is our current playback position to start from
1025 : ///
1026 : /// stopAtMeasureEnd being set to true will have the loop
1027 : /// stop at measure end.
1028 : //---------------------------------------------------------
1029 482 : Fraction Harmony::ticksTillNext(int utick, bool stopAtMeasureEnd) const
1030 : {
1031 482 : const Segment* seg = getParentSeg();
1032 482 : if (!seg) {
1033 0 : return Fraction(-1, 1);
1034 : }
1035 :
1036 482 : Fraction duration = seg->ticks();
1037 :
1038 482 : const RepeatList& repeats = score()->repeatList();
1039 482 : auto rsIt = repeats.findRepeatSegmentFromUTick(utick);
1040 482 : if (rsIt == repeats.cend()) {
1041 0 : return duration;
1042 : }
1043 :
1044 482 : Segment* cur = seg->next();
1045 482 : Measure const* currentMeasure = seg->measure();
1046 482 : Measure const* endMeasure = (stopAtMeasureEnd) ? currentMeasure : (*rsIt)->lastMeasure();
1047 482 : Harmony const* nextHarmony = nullptr;
1048 :
1049 : do {
1050 : // Loop over segments of this measure
1051 1597 : while (cur) {
1052 : //find harmony on same track
1053 1115 : EngravingItem* e = cur->findAnnotation(ElementType::HARMONY, track(), track());
1054 1115 : if (e) {
1055 471 : nextHarmony = toHarmony(e);
1056 : } else {
1057 : // no harmony; look for fret diagram
1058 644 : e = cur->findAnnotation(ElementType::FRET_DIAGRAM, track(), track());
1059 644 : if (e) {
1060 0 : nextHarmony = toFretDiagram(e)->harmony();
1061 : }
1062 : }
1063 1115 : if (nextHarmony != nullptr) {
1064 : //we have found the next chord symbol
1065 471 : break;
1066 : }
1067 : //keep adding the duration of the current segment
1068 : //in case we are not able to find a next
1069 : //chord symbol
1070 644 : duration += cur->ticks();
1071 644 : cur = cur->next();
1072 : }
1073 : // Move segment reference to next measure
1074 953 : if (currentMeasure != endMeasure) {
1075 932 : currentMeasure = currentMeasure->nextMeasure();
1076 932 : if (currentMeasure && currentMeasure->mmRest()) {
1077 0 : duration += currentMeasure->mmRest()->ticks();
1078 0 : currentMeasure = currentMeasure->mmRest()->nextMeasure();
1079 : }
1080 932 : cur = (currentMeasure) ? currentMeasure->first() : nullptr;
1081 : } else {
1082 : // End of repeatSegment or search boundary reached
1083 21 : if (stopAtMeasureEnd) {
1084 3 : break;
1085 18 : } else if (!nextHarmony) {
1086 : // move to next RepeatSegment
1087 9 : if (++rsIt != repeats.end()) {
1088 0 : currentMeasure = (*rsIt)->firstMeasure();
1089 0 : endMeasure = (*rsIt)->lastMeasure();
1090 0 : cur = currentMeasure->first();
1091 : }
1092 : }
1093 : }
1094 950 : } while ((nextHarmony == nullptr) && (cur != nullptr));
1095 :
1096 482 : if (nextHarmony && rsIt != repeats.end()) {
1097 471 : int tickOffset = (*rsIt)->utick - (*rsIt)->tick;
1098 471 : int nextHarmonyUtick = nextHarmony->tick().ticks() + tickOffset;
1099 471 : duration = Fraction::fromTicks(nextHarmonyUtick - utick);
1100 : }
1101 :
1102 482 : return duration;
1103 : }
1104 :
1105 : //---------------------------------------------------------
1106 : // getRealizedHarmony
1107 : // get realized harmony or create one for the current symbol
1108 : // also updates the realized harmony and accounts for
1109 : // transposition. RealizedHarmony objects cannot be cached
1110 : // since the notes generated depends on context rather than
1111 : // just root, bass, chord symbol, and voicing.
1112 : //---------------------------------------------------------
1113 :
1114 492 : const RealizedHarmony& Harmony::getRealizedHarmony() const
1115 : {
1116 492 : const Staff* st = staff();
1117 492 : IF_ASSERT_FAILED(st) {
1118 0 : return m_realizedHarmony;
1119 : }
1120 :
1121 492 : const Fraction tick = this->tick();
1122 492 : const CapoParams& capo = st->capo(tick);
1123 :
1124 492 : int offset = 0;
1125 492 : if (capo.active && CapoParams::TransposeMode::TAB_ONLY != capo.transposeMode) {
1126 0 : offset = capo.fretPosition;
1127 : }
1128 :
1129 492 : Interval interval = originPart()->instrument(tick)->transpose();
1130 492 : if (!style().styleB(Sid::concertPitch)) {
1131 467 : offset += interval.chromatic;
1132 : }
1133 :
1134 492 : HarmonyInfo* info = m_chords.empty() ? nullptr : m_chords.front();
1135 492 : int root = info ? info->rootTpc() : Tpc::TPC_INVALID;
1136 492 : int bass = info ? info->bassTpc() : Tpc::TPC_INVALID;
1137 :
1138 492 : m_realizedHarmony.update(root, bass, offset);
1139 :
1140 492 : return m_realizedHarmony;
1141 : }
1142 :
1143 : //---------------------------------------------------------
1144 : // realizedHarmony
1145 : // get realized harmony or create one for the current symbol
1146 : // without updating the realized harmony
1147 : //---------------------------------------------------------
1148 :
1149 106 : RealizedHarmony& Harmony::realizedHarmony()
1150 : {
1151 106 : return m_realizedHarmony;
1152 : }
1153 :
1154 571 : const ParsedChord* Harmony::parsedForm()const
1155 : {
1156 571 : if (m_chords.empty()) {
1157 0 : return nullptr;
1158 : }
1159 571 : return m_chords.front()->getParsedChord();
1160 : }
1161 :
1162 0 : Color Harmony::curColor(const rendering::PaintOptions& opt) const
1163 : {
1164 0 : if (!opt.isPrinting && m_isMisspelled) {
1165 0 : return configuration()->criticalColor();
1166 : }
1167 :
1168 0 : return EngravingItem::curColor(opt);
1169 : }
1170 :
1171 1229 : void Harmony::setColor(const Color& color)
1172 : {
1173 1229 : EngravingItem::setColor(color);
1174 :
1175 1371 : for (HarmonyRenderItem* item : ldata()->renderItemList()) {
1176 142 : if (ChordSymbolParen* paren = dynamic_cast<ChordSymbolParen*>(item)) {
1177 8 : paren->parenItem->setColor(color);
1178 : }
1179 : }
1180 1229 : }
1181 :
1182 : //---------------------------------------------------------
1183 : // width
1184 : //---------------------------------------------------------
1185 :
1186 10778 : double TextSegment::width() const
1187 : {
1188 10778 : return FontMetrics::width(m_font, m_text);
1189 : }
1190 :
1191 14234 : double TextSegment::capHeight() const
1192 : {
1193 14234 : return FontMetrics::capHeight(m_font);
1194 : }
1195 :
1196 53538 : RectF TextSegment::tightBoundingRect() const
1197 : {
1198 53538 : return FontMetrics::tightBoundingRect(m_font, m_text);
1199 : }
1200 :
1201 0 : RectF TextSegment::boundingRect() const
1202 : {
1203 0 : return FontMetrics::boundingRect(m_font, m_text);
1204 : }
1205 :
1206 10682 : void TextSegment::setFont(const muse::draw::Font& f)
1207 : {
1208 10682 : m_font = f;
1209 10682 : if (f.type() != Font::Type::MusicSymbolText) {
1210 10522 : return;
1211 : }
1212 :
1213 : // Check all symbols in string are available in this font
1214 160 : FontMetrics fm(f);
1215 160 : bool fail = false;
1216 320 : for (size_t i = 0; i < m_text.size(); ++i) {
1217 160 : const Char& c = m_text.at(i);
1218 160 : if (c.isHighSurrogate()) {
1219 0 : if (i + 1 == m_text.size()) {
1220 0 : ASSERT_X("bad string");
1221 : }
1222 0 : const Char& c2 = m_text.at(i + 1);
1223 0 : ++i;
1224 0 : char32_t v = Char::surrogateToUcs4(c, c2);
1225 0 : if (!fm.inFont(v)) {
1226 0 : fail = true;
1227 0 : break;
1228 : }
1229 : } else {
1230 160 : if (!fm.inFont(c.unicode())) {
1231 0 : fail = true;
1232 0 : break;
1233 : }
1234 : }
1235 : }
1236 : // Fallback to default musical text font
1237 160 : if (fail) {
1238 0 : m_font.setFamily(String::fromUtf8(FALLBACK_SYMBOLTEXT_FONT), Font::Type::MusicSymbolText);
1239 : }
1240 160 : }
1241 :
1242 : //---------------------------------------------------------
1243 : // spatiumChanged
1244 : //---------------------------------------------------------
1245 :
1246 5 : void Harmony::spatiumChanged(double oldValue, double newValue)
1247 : {
1248 5 : TextBase::spatiumChanged(oldValue, newValue);
1249 5 : }
1250 :
1251 : //---------------------------------------------------------
1252 : // localSpatiumChanged
1253 : //---------------------------------------------------------
1254 :
1255 0 : void Harmony::localSpatiumChanged(double oldValue, double newValue)
1256 : {
1257 0 : TextBase::localSpatiumChanged(oldValue, newValue);
1258 0 : }
1259 :
1260 : //---------------------------------------------------------
1261 : // addDegree
1262 : //---------------------------------------------------------
1263 :
1264 0 : void Harmony::addDegree(const HDegree& d)
1265 : {
1266 0 : m_degreeList.push_back(d);
1267 0 : }
1268 :
1269 : //---------------------------------------------------------
1270 : // degreeList
1271 : //---------------------------------------------------------
1272 :
1273 748 : const std::vector<HDegree>& Harmony::degreeList() const
1274 : {
1275 748 : return m_degreeList;
1276 : }
1277 :
1278 : //---------------------------------------------------------
1279 : // setHarmonyType
1280 : //---------------------------------------------------------
1281 :
1282 92 : void Harmony::setHarmonyType(HarmonyType val)
1283 : {
1284 92 : m_harmonyType = val;
1285 92 : setPlacement(propertyDefault(Pid::PLACEMENT).value<PlacementV>());
1286 92 : switch (m_harmonyType) {
1287 26 : case HarmonyType::STANDARD:
1288 26 : initTextStyleType(TextStyleType::HARMONY_A);
1289 26 : break;
1290 23 : case HarmonyType::ROMAN:
1291 23 : initTextStyleType(TextStyleType::HARMONY_ROMAN);
1292 23 : break;
1293 43 : case HarmonyType::NASHVILLE:
1294 43 : initTextStyleType(TextStyleType::HARMONY_NASHVILLE);
1295 43 : break;
1296 : }
1297 : // TODO: convert text
1298 92 : }
1299 :
1300 : //---------------------------------------------------------
1301 : // typeUserName
1302 : //---------------------------------------------------------
1303 :
1304 0 : TranslatableString Harmony::typeUserName() const
1305 : {
1306 0 : switch (m_harmonyType) {
1307 0 : case HarmonyType::ROMAN:
1308 0 : return TranslatableString("engraving", "Roman numeral");
1309 0 : case HarmonyType::NASHVILLE:
1310 0 : return TranslatableString("engraving", "Nashville number");
1311 0 : case HarmonyType::STANDARD:
1312 0 : break;
1313 : }
1314 0 : return EngravingItem::typeUserName();
1315 : }
1316 :
1317 : //---------------------------------------------------------
1318 : // accessibleInfo
1319 : //---------------------------------------------------------
1320 :
1321 0 : String Harmony::accessibleInfo() const
1322 : {
1323 0 : return String(u"%1: %2").arg(translatedTypeUserName(), harmonyName());
1324 : }
1325 :
1326 : //---------------------------------------------------------
1327 : // screenReaderInfo
1328 : //---------------------------------------------------------
1329 :
1330 0 : String Harmony::screenReaderInfo() const
1331 : {
1332 0 : return String(u"%1 %2").arg(translatedTypeUserName(), generateScreenReaderInfo());
1333 : }
1334 :
1335 : //---------------------------------------------------------
1336 : // generateScreenReaderInfo
1337 : //---------------------------------------------------------
1338 :
1339 0 : String Harmony::generateScreenReaderInfo() const
1340 : {
1341 0 : String rez;
1342 0 : for (size_t i = 0; i < m_chords.size(); i++) {
1343 0 : HarmonyInfo* info = m_chords.at(i);
1344 0 : if (!tpcIsValid(info->rootTpc())) {
1345 0 : continue;
1346 : }
1347 0 : if (i != 0) {
1348 0 : rez += u" | ";
1349 : }
1350 :
1351 0 : switch (m_harmonyType) {
1352 0 : case HarmonyType::ROMAN: {
1353 0 : if (m_chords.empty()) {
1354 0 : return u"";
1355 : }
1356 0 : String aux = info->textName();
1357 0 : bool hasUpper = aux.contains(u'I') || aux.contains(u'V');
1358 0 : bool hasLower = aux.contains(u'i') || aux.contains(u'v');
1359 0 : if (hasLower && !hasUpper) {
1360 0 : rez = String(u"%1 %2").arg(rez, muse::mtrc("engraving", "lower case"));
1361 : }
1362 0 : aux = aux.toLower();
1363 : static const std::vector<std::pair<String, String> > rnaReplacements {
1364 : { u"vii", u"7" },
1365 : { u"vi", u"6" },
1366 : { u"iv", u"4" },
1367 : { u"v", u"5" },
1368 : { u"iii", u"3" },
1369 : { u"ii", u"2" },
1370 : { u"i", u"1" },
1371 0 : };
1372 : static const std::vector<std::pair<String, String> > symbolReplacements {
1373 : { u"bb", u"𝄫" },
1374 : { u"##", u"𝄪" },
1375 : { u"h", u"♮" },
1376 : { u"\\♮", u"h" }, // \h should be h, so need to correct replacing in the previous step
1377 : { u"#", u"♯" },
1378 : { u"\\♯", u"#" }, // \# should be #, so need to correct replacing in the previous step
1379 : { u"b", u"♭" },
1380 : { u"\\♭", u"b" }, // \b should be b, so need to correct replacing in the previous step
1381 : // TODO: use SMuFL glyphs and translate
1382 : //{ "o", ""},
1383 : //{ "0", ""},
1384 : //{ "\+", ""},
1385 : //{ "\^", ""},
1386 0 : };
1387 0 : for (auto const& r : rnaReplacements) {
1388 0 : aux.replace(r.first, r.second);
1389 : }
1390 0 : for (auto const& r : symbolReplacements) {
1391 0 : aux.replace(r.first, r.second);
1392 : }
1393 : // construct string one character at a time
1394 0 : for (size_t j = 0; j < aux.size(); ++j) {
1395 0 : rez = String(u"%1 %2").arg(rez).arg(aux.at(j));
1396 : }
1397 0 : }
1398 0 : return rez;
1399 0 : case HarmonyType::NASHVILLE: {
1400 0 : const Staff* st = staff();
1401 0 : Key key = st ? st->key(tick()) : Key::INVALID;
1402 0 : rez = String(u"%1 %2").arg(rez, tpc2Function(info->rootTpc(), key));
1403 0 : break;
1404 : }
1405 0 : case HarmonyType::STANDARD:
1406 : default:
1407 0 : rez = String(u"%1 %2").arg(rez, tpc2name(info->rootTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));
1408 : }
1409 :
1410 0 : if (!info->textName().isEmpty()) {
1411 0 : String aux = info->getParsedChord()->handle();
1412 0 : aux = aux.replace(u"#", u"♯").replace(u"<", u"");
1413 0 : String extension;
1414 :
1415 0 : for (String s : aux.split(u'>', muse::SkipEmptyParts)) {
1416 0 : if (!s.contains(u"blues")) {
1417 0 : s.replace(u"b", u"♭");
1418 : }
1419 0 : extension += s + u' ';
1420 0 : }
1421 0 : rez = String(u"%1 %2").arg(rez, extension);
1422 0 : } else {
1423 0 : rez = String(u"%1 %2").arg(rez, info->textName());
1424 : }
1425 :
1426 0 : if (tpcIsValid(info->bassTpc())) {
1427 0 : rez = String(u"%1 / %2").arg(rez, tpc2name(info->bassTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, true));
1428 : }
1429 : }
1430 :
1431 0 : return rez;
1432 0 : }
1433 :
1434 : //---------------------------------------------------------
1435 : // acceptDrop
1436 : //---------------------------------------------------------
1437 :
1438 0 : bool Harmony::acceptDrop(EditData& data) const
1439 : {
1440 0 : EngravingItem* e = data.dropElement;
1441 0 : if (e->isFretDiagram()) {
1442 0 : return true;
1443 0 : } else if (e->isSymbol() || e->isFSymbol()) {
1444 : // symbols can be added in edit mode
1445 0 : if (data.getData(this)) {
1446 0 : return true;
1447 : } else {
1448 0 : return false;
1449 : }
1450 : }
1451 0 : return false;
1452 : }
1453 :
1454 : //---------------------------------------------------------
1455 : // drop
1456 : //---------------------------------------------------------
1457 :
1458 0 : EngravingItem* Harmony::drop(Transaction& tx, EditData& data)
1459 : {
1460 0 : EngravingItem* e = data.dropElement;
1461 0 : if (e->isFretDiagram()) {
1462 0 : FretDiagram* fd = toFretDiagram(e);
1463 0 : fd->setOwnershipParent(ownershipParent());
1464 0 : fd->setTrack(track());
1465 0 : score()->undoAddElement(fd);
1466 0 : } else if (e->isSymbol() || e->isFSymbol()) {
1467 0 : TextBase::drop(tx, data);
1468 0 : renderer()->layoutText1(this);
1469 0 : e = 0; // cannot select
1470 : } else {
1471 0 : LOGW("Harmony: cannot drop <%s>\n", e->typeName());
1472 0 : delete e;
1473 0 : e = 0;
1474 : }
1475 0 : return e;
1476 : }
1477 :
1478 0 : void Harmony::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
1479 : {
1480 0 : if (id == Pid::FONT_STYLE || id == Pid::FONT_FACE || id == Pid::FONT_SIZE) {
1481 0 : EngravingItem::undoChangeProperty(id, v, ps);
1482 0 : } else if (id == Pid::EXCLUDE_VERTICAL_ALIGN) {
1483 0 : TextBase::undoChangeProperty(id, v, ps);
1484 :
1485 0 : bool val = v.toBool();
1486 0 : FretDiagram* fd = getParentFretDiagram();
1487 0 : if (fd && fd->excludeVerticalAlign() != val) {
1488 0 : fd->undoChangeProperty(Pid::EXCLUDE_VERTICAL_ALIGN, val, ps);
1489 : }
1490 0 : Segment* parentSeg = getParentSeg();
1491 0 : if (!parentSeg) {
1492 0 : return;
1493 : }
1494 0 : for (EngravingItem* item : parentSeg->annotations()) {
1495 0 : if ((!item->isFretDiagram() && !item->isHarmony()) || item == this || track2staff(item->track()) != staffIdx()) {
1496 0 : continue;
1497 : }
1498 :
1499 0 : if (item->excludeVerticalAlign() != val) {
1500 0 : item->undoChangeProperty(Pid::EXCLUDE_VERTICAL_ALIGN, val, ps);
1501 : }
1502 : }
1503 0 : return;
1504 : }
1505 :
1506 0 : TextBase::undoChangeProperty(id, v, ps);
1507 : }
1508 :
1509 : //---------------------------------------------------------
1510 : // getProperty
1511 : //---------------------------------------------------------
1512 :
1513 9866 : PropertyValue Harmony::getProperty(Pid pid) const
1514 : {
1515 9866 : switch (pid) {
1516 748 : case Pid::PLAY:
1517 748 : return PropertyValue(m_play);
1518 748 : case Pid::HARMONY_TYPE:
1519 748 : return PropertyValue(int(m_harmonyType));
1520 0 : case Pid::HARMONY_BASS_SCALE:
1521 0 : return m_bassScale;
1522 25 : case Pid::HARMONY_VOICE_LITERAL:
1523 25 : return m_realizedHarmony.literal();
1524 25 : case Pid::HARMONY_VOICING:
1525 25 : return int(m_realizedHarmony.voicing());
1526 6 : case Pid::HARMONY_DURATION:
1527 6 : return int(m_realizedHarmony.duration());
1528 748 : case Pid::HARMONY_DO_NOT_STACK_MODIFIERS:
1529 748 : return m_doNotStackModifiers;
1530 0 : case Pid::FONT_SIZE:
1531 0 : return m_fontSize;
1532 0 : case Pid::FONT_STYLE:
1533 0 : return static_cast<int>(m_fontStyle);
1534 0 : case Pid::FONT_FACE:
1535 0 : return m_fontFamily;
1536 7566 : default:
1537 7566 : return TextBase::getProperty(pid);
1538 : }
1539 : }
1540 :
1541 : //---------------------------------------------------------
1542 : // setProperty
1543 : //---------------------------------------------------------
1544 :
1545 27079 : bool Harmony::setProperty(Pid pid, const PropertyValue& v)
1546 : {
1547 27079 : switch (pid) {
1548 0 : case Pid::PLAY:
1549 0 : m_play = v.toBool();
1550 0 : break;
1551 29 : case Pid::HARMONY_TYPE:
1552 29 : setHarmonyType(HarmonyType(v.toInt()));
1553 29 : break;
1554 1134 : case Pid::HARMONY_BASS_SCALE:
1555 1134 : setBassScale(v.toDouble());
1556 1134 : break;
1557 1159 : case Pid::HARMONY_VOICE_LITERAL:
1558 1159 : m_realizedHarmony.setLiteral(v.toBool());
1559 1159 : break;
1560 1579 : case Pid::HARMONY_VOICING:
1561 1579 : m_realizedHarmony.setVoicing(Voicing(v.toInt()));
1562 1579 : break;
1563 1140 : case Pid::HARMONY_DURATION:
1564 1140 : m_realizedHarmony.setDuration(HDuration(v.toInt()));
1565 1140 : break;
1566 0 : case Pid::EXCLUDE_VERTICAL_ALIGN: {
1567 0 : setExcludeVerticalAlign(v.toBool());
1568 0 : break;
1569 : }
1570 0 : case Pid::HARMONY_DO_NOT_STACK_MODIFIERS:
1571 0 : m_doNotStackModifiers = v.toBool();
1572 0 : break;
1573 0 : case Pid::TEXT:
1574 : {
1575 0 : String curText = xmlText();
1576 0 : TextBase::setProperty(pid, v);
1577 0 : setHarmony(v.value<String>());
1578 0 : String newText = xmlText();
1579 0 : if (newText != curText) {
1580 0 : FretDiagram* fretDiagram = ownershipParent()->isFretDiagram() ? toFretDiagram(ownershipParent()) : nullptr;
1581 0 : if (fretDiagram && !fretDiagram->isCustom(curText) && configuration()->autoUpdateFretboardDiagrams()) {
1582 0 : fretDiagram->updateDiagram(plainText());
1583 : }
1584 0 : score()->rebuildFretBox();
1585 : }
1586 0 : break;
1587 0 : }
1588 22038 : default:
1589 22038 : return TextBase::setProperty(pid, v);
1590 : }
1591 5041 : triggerLayout();
1592 5041 : return true;
1593 : }
1594 :
1595 : //---------------------------------------------------------
1596 : // propertyDefault
1597 : //---------------------------------------------------------
1598 :
1599 10264 : PropertyValue Harmony::propertyDefault(Pid id) const
1600 : {
1601 10264 : PropertyValue v;
1602 10264 : switch (id) {
1603 748 : case Pid::HARMONY_TYPE:
1604 748 : v = int(HarmonyType::STANDARD);
1605 748 : break;
1606 748 : case Pid::TEXT_STYLE: {
1607 748 : switch (m_harmonyType) {
1608 733 : case HarmonyType::STANDARD:
1609 733 : v = TextStyleType::HARMONY_A;
1610 733 : break;
1611 15 : case HarmonyType::ROMAN:
1612 15 : v = TextStyleType::HARMONY_ROMAN;
1613 15 : break;
1614 0 : case HarmonyType::NASHVILLE:
1615 0 : v = TextStyleType::HARMONY_NASHVILLE;
1616 0 : break;
1617 : }
1618 : }
1619 748 : break;
1620 0 : case Pid::HARMONY_BASS_SCALE:
1621 0 : v = style().styleV(Sid::chordBassNoteScale).toDouble();
1622 0 : break;
1623 748 : case Pid::HARMONY_DO_NOT_STACK_MODIFIERS:
1624 748 : return false;
1625 748 : case Pid::PLAY:
1626 748 : v = true;
1627 748 : break;
1628 : // fall-through
1629 7272 : default:
1630 7272 : v = TextBase::propertyDefault(id);
1631 7272 : break;
1632 : }
1633 9516 : return v;
1634 10264 : }
1635 :
1636 94 : bool Harmony::positionRelativeToNoteheadRest() const
1637 : {
1638 94 : return !parent()->isFretDiagram();
1639 : }
1640 :
1641 : //---------------------------------------------------------
1642 : // getPropertyStyle
1643 : //---------------------------------------------------------
1644 :
1645 7910 : Sid Harmony::getPropertyStyle(Pid pid) const
1646 : {
1647 7910 : if (pid == Pid::PLACEMENT) {
1648 163 : switch (m_harmonyType) {
1649 97 : case HarmonyType::STANDARD:
1650 97 : return Sid::harmonyPlacement;
1651 23 : case HarmonyType::ROMAN:
1652 23 : return Sid::romanNumeralPlacement;
1653 43 : case HarmonyType::NASHVILLE:
1654 43 : return Sid::nashvilleNumberPlacement;
1655 : }
1656 : }
1657 7747 : return TextBase::getPropertyStyle(pid);
1658 : }
1659 :
1660 134105 : double Harmony::mag() const
1661 : {
1662 134105 : return EngravingItem::mag();
1663 : }
1664 :
1665 66 : HarmonyInfo::HarmonyInfo(const HarmonyInfo& h)
1666 : {
1667 66 : m_id = h.m_id;
1668 66 : m_bassTpc = h.m_bassTpc;
1669 66 : m_rootTpc = h.m_rootTpc;
1670 66 : m_textName = h.m_textName;
1671 66 : m_score = h.m_score;
1672 66 : m_parsedChord = h.m_parsedChord ? new ParsedChord(*h.m_parsedChord) : 0;
1673 66 : }
1674 :
1675 1103 : HarmonyInfo::~HarmonyInfo()
1676 : {
1677 1103 : if (m_parsedChord) {
1678 774 : delete m_parsedChord;
1679 : }
1680 1103 : }
1681 : }
|