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 "../iengravingconfiguration.h" // IWYU pragma: keep
24 :
25 : #include "../editing/editdata.h"
26 : #include "../editing/elementeditdata.h"
27 : #include "../editing/editnote.h"
28 : #include "../editing/navigation.h"
29 : #include "../editing/noteinput.h"
30 : #include "../editing/transaction/transaction.h"
31 : #include "../editing/transpose.h"
32 :
33 : #include "accidental.h"
34 : #include "actionicon.h"
35 : #include "chord.h"
36 : #include "guitarbend.h"
37 : #include "note.h"
38 : #include "part.h"
39 : #include "rest.h"
40 : #include "score.h"
41 : #include "staff.h"
42 : #include "tie.h"
43 : #include "utils.h"
44 : #include "whammybar.h"
45 :
46 : namespace mu::engraving {
47 : /****************************************
48 : * GuitarBend
49 : * **************************************/
50 :
51 395 : GuitarBend::GuitarBend(EngravingObject* parent)
52 395 : : SLine(ElementType::GUITAR_BEND, parent, ElementFlag::MOVABLE)
53 : {
54 395 : }
55 :
56 4 : GuitarBend::GuitarBend(const GuitarBend& g)
57 4 : : SLine(g)
58 : {
59 4 : m_bendType = g.bendType();
60 4 : _bendAmountInQuarterTones = g.bendAmountInQuarterTones();
61 4 : }
62 :
63 682 : GuitarBend::~GuitarBend()
64 : {
65 341 : if (m_holdLine) {
66 2 : delete m_holdLine;
67 : }
68 682 : }
69 :
70 393 : LineSegment* GuitarBend::createLineSegment()
71 : {
72 393 : GuitarBendSegment* seg = new GuitarBendSegment(this);
73 393 : seg->setTrack(track());
74 393 : seg->setColor(lineColor());
75 393 : return seg;
76 : }
77 :
78 16448 : Note* GuitarBend::startNote() const
79 : {
80 16448 : EngravingItem* startEl = startElement();
81 16448 : IF_ASSERT_FAILED(startEl && startEl->isNote()) {
82 0 : return nullptr;
83 : }
84 16448 : return toNote(startEl);
85 : }
86 :
87 162 : Note* GuitarBend::startNoteOfChain() const
88 : {
89 162 : Note* startOfChain = startNote();
90 162 : GuitarBend* prevBend = findPrecedingBend();
91 235 : while (startOfChain && prevBend) {
92 73 : startOfChain = prevBend->startNote();
93 73 : prevBend = prevBend->findPrecedingBend();
94 : }
95 :
96 162 : return startOfChain;
97 : }
98 :
99 6259 : Note* GuitarBend::endNote() const
100 : {
101 6259 : EngravingItem* endEl = endElement();
102 6259 : IF_ASSERT_FAILED(endEl && endEl->isNote()) {
103 0 : return nullptr;
104 : }
105 6259 : return toNote(endEl);
106 : }
107 :
108 0 : void GuitarBend::changeBendAmount(int endBendAmount, int startBendAmount)
109 : {
110 0 : if (endBendAmount == SLACK_BEND_AMOUNT) {
111 0 : undoChangeProperty(Pid::GUITAR_DIVE_IS_SLACK, true);
112 0 : if (endNote()) {
113 0 : endNote()->undoChangeProperty(Pid::HEAD_GROUP, NoteHeadGroup::HEAD_CROSS);
114 : }
115 0 : return;
116 0 : } else if (isSlack()) {
117 0 : undoResetProperty(Pid::GUITAR_DIVE_IS_SLACK);
118 0 : if (endNote()) {
119 0 : endNote()->undoResetProperty(Pid::HEAD_GROUP);
120 : }
121 : }
122 :
123 0 : if (bendType() == GuitarBendType::DIP) {
124 : // Dips and slack have no end note so set bend amount directly
125 0 : undoChangeProperty(Pid::GUITAR_BEND_AMOUNT, endBendAmount);
126 0 : return;
127 : }
128 :
129 0 : if (GuitarBend* overlapping = overlappingBendOrDive()) {
130 0 : int curTotalBendAmount = bendAmountInQuarterTones() + overlapping->bendAmountInQuarterTones();
131 0 : int newBendAmountForThis = endBendAmount - startBendAmount;
132 0 : if (isDive()) {
133 0 : undoChangeProperty(Pid::GUITAR_BEND_AMOUNT, newBendAmountForThis);
134 : } else {
135 0 : overlapping->undoChangeProperty(Pid::GUITAR_BEND_AMOUNT, curTotalBendAmount - newBendAmountForThis);
136 : }
137 0 : return;
138 : }
139 :
140 : // All other bends: set bend amount by transposing end note appropriately
141 0 : int pitch = endBendAmount / 2 + startNoteOfChain()->pitch();
142 0 : int quarterOff = endBendAmount % 2;
143 0 : if (pitch == startNote()->pitch() && quarterOff == 1) {
144 : // Because a flat second is more readable than a sharp unison
145 0 : pitch += 1;
146 0 : quarterOff = -1;
147 : }
148 :
149 0 : setEndNotePitch(pitch, quarterOff);
150 : }
151 :
152 237 : void GuitarBend::setEndNotePitch(int pitch, int quarterToneOffset)
153 : {
154 237 : Note* note = endNote();
155 237 : IF_ASSERT_FAILED(note) {
156 0 : return;
157 : }
158 :
159 237 : Fraction tick = note->tick();
160 237 : Staff* staff = note->staff();
161 :
162 237 : Key key = staff->key(tick);
163 237 : Interval interval = staff->transpose(tick);
164 237 : interval.flip();
165 :
166 237 : int targetTpc1 = pitch2tpc(pitch, key, Prefer::NEAREST);
167 237 : int targetTpc2 = Transpose::transposeTpc(targetTpc1, interval, true);
168 :
169 237 : auto doChangeEndNotePitch = [&]() {
170 237 : EditNote::undoChangePitch(score(), note, pitch, targetTpc1, targetTpc2);
171 237 : Note* tiedNote = note->tieFor() ? note->tieFor()->endNote() : nullptr;
172 261 : while (tiedNote) {
173 24 : EditNote::undoChangePitch(score(), tiedNote, pitch, targetTpc1, targetTpc2);
174 24 : tiedNote = tiedNote->tieFor() ? tiedNote->tieFor()->endNote() : nullptr;
175 : }
176 474 : };
177 :
178 237 : Note* linkedNoteOnNotationStaff = nullptr;
179 237 : for (EngravingObject* linked : note->linkList()) {
180 237 : if (!toNote(linked)->staffType()->isTabStaff()) {
181 237 : linkedNoteOnNotationStaff = toNote(linked);
182 237 : break;
183 : }
184 237 : }
185 :
186 237 : if (linkedNoteOnNotationStaff) {
187 : // Manage microtonal by setting appropriate microtonal accidentals, which will propagate to TAB staff too
188 237 : AccidentalType accidentalType = Accidental::value2MicrotonalSubtype(tpc2alter(targetTpc1), quarterToneOffset);
189 237 : if (Accidental::isMicrotonal(accidentalType)) {
190 12 : doChangeEndNotePitch();
191 12 : linkedNoteOnNotationStaff->updateLine();
192 12 : EditNote::changeAccidental(score(), linkedNoteOnNotationStaff, accidentalType);
193 : } else {
194 225 : linkedNoteOnNotationStaff->updateLine();
195 225 : EditNote::changeAccidental(score(), linkedNoteOnNotationStaff, accidentalType);
196 225 : doChangeEndNotePitch();
197 : }
198 : } else {
199 : // Accidental logic doesn't work on TAB, so set cents offset directly
200 0 : doChangeEndNotePitch();
201 0 : note->undoChangeProperty(Pid::CENT_OFFSET, quarterToneOffset * 50.0);
202 : }
203 :
204 237 : computeBendAmount();
205 :
206 237 : triggerLayout();
207 : }
208 :
209 664 : bool GuitarBend::isReleaseBend() const
210 : {
211 664 : return bendAmountInQuarterTones() < 0;
212 : }
213 :
214 212 : bool GuitarBend::isFullRelease() const
215 : {
216 212 : return isReleaseBend() && totBendAmountIncludingPrecedingBends() == 0;
217 : }
218 :
219 112 : bool GuitarBend::angledPreBend() const
220 : {
221 112 : Note* endN = endNote();
222 112 : Chord* endChord = endNote()->chord();
223 112 : return bendType() == GuitarBendType::PRE_BEND && endN->string() > endChord->upString();
224 : }
225 :
226 45153 : bool GuitarBend::isDive() const
227 : {
228 45153 : switch (bendType()) {
229 19676 : case GuitarBendType::DIVE:
230 : case GuitarBendType::PRE_DIVE:
231 : case GuitarBendType::DIP:
232 : case GuitarBendType::SCOOP:
233 19676 : return true;
234 25477 : default:
235 25477 : return false;
236 : }
237 : }
238 :
239 0 : bool GuitarBend::isFullReleaseDive() const
240 : {
241 0 : return isDive() && totBendAmountIncludingPrecedingBends() == 0;
242 : }
243 :
244 120 : void GuitarBend::fixNotesFrettingForStandardBend(Note* startNote, Note* endNote)
245 : {
246 120 : Staff* curStaff = startNote->staff();
247 120 : Part* curPart = startNote->originPart();
248 120 : if (!curStaff || !curPart) {
249 0 : return;
250 : }
251 :
252 120 : const StringData* stringData = curPart->stringData(startNote->tick(), curStaff->idx());
253 120 : int startFret = startNote->fret();
254 120 : int startString = startNote->string();
255 120 : if (startFret == 0) {
256 : // Bend can't start from empty string
257 1 : startString += 1;
258 1 : startFret = stringData->fret(startNote->pitch() + curStaff->pitchOffset(startNote->tick()), startString, curStaff);
259 1 : startNote->undoChangeProperty(Pid::STRING, startString);
260 1 : startNote->undoChangeProperty(Pid::FRET, startFret);
261 : }
262 :
263 120 : if (endNote->string() != startString) {
264 73 : endNote->undoChangeProperty(Pid::STRING, startString);
265 73 : int endFret = stringData->fret(endNote->pitch() + curStaff->pitchOffset(endNote->tick()), startString, curStaff);
266 73 : endNote->undoChangeProperty(Pid::FRET, endFret);
267 : }
268 : }
269 :
270 0 : Note* GuitarBend::createEndNote(Note* startNote, GuitarBendType bendType)
271 : {
272 0 : track_idx_t track = startNote->track();
273 0 : Chord* startChord = startNote->chord();
274 0 : Segment* startSegment = startChord->segment();
275 :
276 0 : Segment* endSegment = startSegment->nextCR(track);
277 0 : if (!endSegment) {
278 0 : return nullptr;
279 : }
280 :
281 0 : EngravingItem* item = endSegment->element(track);
282 0 : if (!item) {
283 0 : return nullptr;
284 : }
285 :
286 0 : Score* score = startNote->score();
287 0 : NoteVal noteVal = startNote->noteVal();
288 0 : Note* endNote = nullptr;
289 :
290 0 : if (item->isRest()) {
291 0 : Rest* rest = toRest(item);
292 0 : Fraction duration = std::min(startChord->ticks(), rest->ticks());
293 :
294 0 : endSegment = score->setNoteRest(endSegment, track, noteVal, duration);
295 0 : Chord* endChord = endSegment ? toChord(endSegment->element(track)) : nullptr;
296 0 : endNote = endChord ? endChord->upNote() : nullptr;
297 0 : } else if (item->isChord()) {
298 0 : Chord* chord = toChord(item);
299 0 : endNote = NoteInput::addNote(score->transactionManager()->currentOrDummyTransaction(), score, chord, noteVal);
300 : }
301 :
302 0 : if (endNote) {
303 0 : endNote->transposeDiatonic(bendType == GuitarBendType::BEND ? 1 : -1, true, false);
304 : }
305 :
306 0 : return endNote;
307 : }
308 :
309 66 : void GuitarBend::fixNotesFrettingForGraceBend(Note* grace, Note* main)
310 : {
311 : // The start grace-note of bend must be on the same string as the main note
312 66 : int mainString = main->string();
313 66 : const StringData* stringData = main->originPart()->stringData(main->tick(), main->staff()->idx());
314 66 : int staffPitchOffset = main->staff()->pitchOffset(main->tick());
315 66 : int graceFret = stringData->fret(grace->pitch() + staffPitchOffset, mainString, main->staff());
316 66 : if (graceFret > 0) {
317 : // There is valid fretting
318 60 : grace->undoChangeProperty(Pid::STRING, mainString);
319 60 : grace->undoChangeProperty(Pid::FRET, graceFret);
320 : } else {
321 : // No valid fretting on this string, so it must be played on the lower string
322 6 : mainString += 1;
323 6 : main->undoChangeProperty(Pid::STRING, mainString);
324 6 : int mainFret = stringData->fret(main->pitch() + staffPitchOffset, mainString, main->staff());
325 6 : main->undoChangeProperty(Pid::FRET, mainFret);
326 :
327 6 : grace->undoChangeProperty(Pid::STRING, mainString);
328 6 : graceFret = stringData->fret(grace->pitch() + staffPitchOffset, mainString, main->staff());
329 6 : grace->undoChangeProperty(Pid::FRET, graceFret);
330 : }
331 66 : }
332 :
333 5290 : PropertyValue GuitarBend::getProperty(Pid id) const
334 : {
335 5290 : switch (id) {
336 304 : case Pid::DIRECTION:
337 304 : return direction();
338 304 : case Pid::BEND_SHOW_HOLD_LINE:
339 304 : return static_cast<int>(showHoldLine());
340 0 : case Pid::BEND_START_TIME_FACTOR:
341 0 : return startTimeFactor();
342 0 : case Pid::BEND_TARGET_TIME_FACTOR:
343 0 : return m_targetTimeFactor.has_value() ? m_targetTimeFactor.value() : PropertyValue();
344 0 : case Pid::BEND_END_TIME_FACTOR:
345 0 : return endTimeFactor();
346 142 : case Pid::GUITAR_DIVE_TAB_POS:
347 142 : return isDive() ? m_diveTabPos : DirectionV::AUTO;
348 0 : case Pid::GUITAR_BEND_AMOUNT:
349 0 : return bendAmountInQuarterTones();
350 142 : case Pid::VIBRATO_LINE_TYPE:
351 142 : return dipVibratoType();
352 142 : case Pid::GUITAR_DIVE_IS_SLACK:
353 142 : return isSlack();
354 4256 : default:
355 4256 : return SLine::getProperty(id);
356 : }
357 : }
358 :
359 1796 : bool GuitarBend::setProperty(Pid propertyId, const PropertyValue& v)
360 : {
361 1796 : switch (propertyId) {
362 0 : case Pid::DIRECTION:
363 0 : setDirection(v.value<DirectionV>());
364 0 : break;
365 0 : case Pid::BEND_SHOW_HOLD_LINE:
366 0 : setShowHoldLine(static_cast<GuitarBendShowHoldLine>(v.toInt()));
367 0 : break;
368 498 : case Pid::BEND_START_TIME_FACTOR:
369 498 : setStartTimeFactor(v.toReal());
370 498 : break;
371 4 : case Pid::BEND_TARGET_TIME_FACTOR:
372 4 : if (v.isValid()) {
373 4 : setTargetTimeFactor(v.toReal());
374 : } else {
375 0 : m_targetTimeFactor = std::nullopt;
376 : }
377 4 : break;
378 498 : case Pid::BEND_END_TIME_FACTOR:
379 498 : setEndTimeFactor(v.toReal());
380 498 : break;
381 0 : case Pid::COLOR:
382 0 : setColor(v.value<Color>());
383 0 : setLineColor(v.value<Color>());
384 0 : break;
385 0 : case Pid::GUITAR_DIVE_TAB_POS:
386 : {
387 0 : IF_ASSERT_FAILED(isDive()) {
388 0 : return false;
389 : }
390 0 : setDiveTabPos(v.value<DirectionV>());
391 0 : break;
392 : }
393 395 : case Pid::GUITAR_BEND_AMOUNT:
394 395 : setBendAmountInQuarterTones(v.toInt());
395 395 : break;
396 0 : case Pid::VIBRATO_LINE_TYPE:
397 : {
398 0 : IF_ASSERT_FAILED(bendType() == GuitarBendType::DIP) {
399 0 : return false;
400 : }
401 0 : setDipVibratoType(v.value<VibratoType>());
402 0 : break;
403 : }
404 0 : case Pid::GUITAR_DIVE_IS_SLACK:
405 0 : setIsSlack(v.toBool());
406 0 : break;
407 401 : default:
408 401 : return SLine::setProperty(propertyId, v);
409 : }
410 1395 : triggerLayout();
411 1395 : return true;
412 : }
413 :
414 6870 : PropertyValue GuitarBend::propertyDefault(Pid id) const
415 : {
416 6870 : switch (id) {
417 304 : case Pid::DIRECTION:
418 304 : return DirectionV::AUTO;
419 304 : case Pid::BEND_SHOW_HOLD_LINE:
420 304 : return static_cast<int>(GuitarBendShowHoldLine::AUTO);
421 395 : case Pid::BEND_START_TIME_FACTOR:
422 395 : return 0.f;
423 395 : case Pid::BEND_TARGET_TIME_FACTOR:
424 395 : if (m_bendType == GuitarBendType::DIP) {
425 2 : return 0.25f;
426 : }
427 393 : return {};
428 395 : case Pid::BEND_END_TIME_FACTOR:
429 395 : if (m_bendType == GuitarBendType::GRACE_NOTE_BEND) {
430 32 : return 0.25f;
431 : }
432 363 : if (m_bendType == GuitarBendType::SCOOP) {
433 1 : return 0.25f;
434 : }
435 362 : if (m_bendType == GuitarBendType::DIP) {
436 2 : return 0.5f;
437 : }
438 360 : return 1.f;
439 142 : case Pid::GUITAR_DIVE_TAB_POS:
440 142 : return DirectionV::AUTO;
441 395 : case Pid::GUITAR_BEND_AMOUNT:
442 395 : return m_bendType == GuitarBendType::DIP ? -2 : 0;
443 142 : case Pid::VIBRATO_LINE_TYPE:
444 284 : return VibratoType::NONE;
445 142 : case Pid::GUITAR_DIVE_IS_SLACK:
446 142 : return false;
447 4256 : default:
448 4256 : return SLine::propertyDefault(id);
449 : }
450 : }
451 :
452 1537 : void GuitarBend::computeBendAmount()
453 : {
454 1537 : if (isSlack()) {
455 0 : int prevAmount = 0;
456 0 : if (GuitarBend* prevBend = findPrecedingBend()) {
457 0 : prevAmount = prevBend->totBendAmountIncludingPrecedingBends();
458 : }
459 0 : setBendAmountInQuarterTones(SLACK_BEND_AMOUNT - prevAmount);
460 0 : computeBendText();
461 80 : return;
462 : }
463 :
464 1537 : if (bendType() == GuitarBendType::DIP) {
465 6 : computeBendText();
466 6 : return;
467 : }
468 :
469 1531 : if (bendType() == GuitarBendType::SCOOP) {
470 3 : setBendAmountInQuarterTones(-2);
471 3 : computeBendText();
472 3 : return;
473 : }
474 :
475 1528 : if (bendType() == GuitarBendType::SLIGHT_BEND) {
476 71 : setBendAmountInQuarterTones(1);
477 71 : computeBendText();
478 71 : return;
479 : }
480 :
481 1457 : if (isDive() && overlappingBendOrDive()) {
482 0 : computeBendText();
483 0 : return;
484 : }
485 :
486 1457 : GuitarBend* prevBend = findPrecedingBend();
487 1457 : GuitarBend* prevSlack = prevBend && prevBend->isSlack() ? prevBend : nullptr;
488 :
489 1457 : Note* startN = prevSlack ? prevSlack->startNote() : startNote();
490 1457 : Note* endN = endNote();
491 :
492 1457 : int startPitch = startN->pitch();
493 1457 : int endPitch = endN->pitch();
494 :
495 1457 : int pitchDiffInQuarterTones = 2 * (endPitch - startPitch);
496 :
497 1457 : int startOffInQuarterTones = round(startN->centOffset() / 50);
498 1457 : int endOffInQuarterTones = round(endN->centOffset() / 50);
499 :
500 1457 : pitchDiffInQuarterTones += endOffInQuarterTones - startOffInQuarterTones;
501 :
502 1457 : if (prevSlack) {
503 0 : pitchDiffInQuarterTones -= SLACK_BEND_AMOUNT;
504 : }
505 :
506 1457 : GuitarBend* overlappingDive = overlappingBendOrDive();
507 1457 : if (overlappingDive) {
508 0 : pitchDiffInQuarterTones -= overlappingDive->bendAmountInQuarterTones();
509 : }
510 :
511 1457 : setBendAmountInQuarterTones(pitchDiffInQuarterTones);
512 :
513 1457 : computeBendText();
514 :
515 1457 : computeIsInvalidOrNeedsWarning();
516 : }
517 :
518 3289 : int GuitarBend::totBendAmountIncludingPrecedingBends() const
519 : {
520 3289 : int bendAmount = bendAmountInQuarterTones();
521 6276 : if (bendType() == GuitarBendType::PRE_DIVE || bendType() == GuitarBendType::PRE_BEND
522 6276 : || bendType() == GuitarBendType::DIP || bendType() == GuitarBendType::SCOOP) {
523 633 : return bendAmount;
524 : }
525 :
526 2656 : GuitarBend* prevBend = findPrecedingBend();
527 4012 : while (prevBend) {
528 1966 : bendAmount += prevBend->bendAmountInQuarterTones();
529 1966 : if (prevBend->bendType() == GuitarBendType::PRE_DIVE || prevBend->bendType() == GuitarBendType::PRE_BEND) {
530 610 : return bendAmount;
531 : }
532 1356 : prevBend = prevBend->findPrecedingBend();
533 : }
534 :
535 2046 : return bendAmount;
536 : }
537 :
538 1537 : void GuitarBend::computeBendText()
539 : {
540 1537 : if (isSlack()) {
541 0 : mutldata()->setBendDigit(String(u"slack"));
542 313 : return;
543 : }
544 :
545 1537 : if (bendType() == GuitarBendType::PRE_DIVE || bendType() == GuitarBendType::PRE_BEND) {
546 265 : GuitarBend* prevBend = findPrecedingBend();
547 265 : if (prevBend && prevBend->holdLine()) {
548 0 : mutldata()->setBendDigit(u"");
549 0 : return;
550 : }
551 : }
552 :
553 1537 : int quarters = totBendAmountIncludingPrecedingBends();
554 :
555 1537 : int fulls = quarters / 4;
556 1537 : int quarts = quarters % 4;
557 1537 : if (fulls == 0 && quarters == 0) {
558 313 : mutldata()->setBendDigit(String());
559 313 : return;
560 : }
561 :
562 1224 : String string = bendAmountToString(fulls, quarts, style().styleB(Sid::useFractionCharacters));
563 :
564 1224 : if (isDive()) {
565 441 : if (quarters > 0) {
566 125 : string.insert(0, u"+");
567 316 : } else if (quarters < 0 && fulls == 0) {
568 19 : string.insert(0, u"-");
569 : }
570 : }
571 :
572 1224 : if (string == u"1" && style().styleB(Sid::guitarBendUseFull)) {
573 0 : string = String(u"full");
574 : }
575 :
576 1224 : mutldata()->setBendDigit(string);
577 1224 : }
578 :
579 1457 : void GuitarBend::computeIsInvalidOrNeedsWarning()
580 : {
581 1457 : int totBendAmount = totBendAmountIncludingPrecedingBends();
582 : static constexpr int UNPLAYABLE_THRESHOLD = 14;
583 : static constexpr int WARNING_THRESHOLD = 8;
584 : static constexpr int DIVE_MAX = 16;
585 : static constexpr int DIVE_MIN = -32;
586 :
587 1457 : if (isDive()) {
588 567 : m_isInvalid = bendAmountInQuarterTones() == 0 || totBendAmount < DIVE_MIN || totBendAmount > DIVE_MAX;
589 567 : m_isBorderlineUnplayable = false;
590 : } else {
591 890 : m_isInvalid = totBendAmount < 0 || totBendAmount > UNPLAYABLE_THRESHOLD || bendAmountInQuarterTones() == 0;
592 890 : m_isBorderlineUnplayable = totBendAmount > WARNING_THRESHOLD && totBendAmount <= UNPLAYABLE_THRESHOLD;
593 : }
594 1457 : }
595 :
596 2965 : GuitarBend* GuitarBend::overlappingBendOrDive() const
597 : {
598 2965 : GuitarBend* overlappingBendOrDive = isDive() ? startNote()->bendFor() : startNote()->diveFor();
599 :
600 1877 : if (overlappingBendOrDive && overlappingBendOrDive != this
601 4842 : && overlappingBendOrDive->endNote() == endNote()) {
602 0 : return overlappingBendOrDive;
603 : }
604 :
605 2965 : return nullptr;
606 : }
607 :
608 6137 : GuitarBend* GuitarBend::findPrecedingBend() const
609 : {
610 6137 : Note* startN = startNote();
611 6549 : while (startN->tieBack() && startN->tieBack()->startNote()) {
612 412 : startN = startN->tieBack()->startNote();
613 : }
614 :
615 2988 : auto isValidType = [](GuitarBendType t) {
616 2988 : switch (t) {
617 0 : case GuitarBendType::SLIGHT_BEND:
618 : case GuitarBendType::DIP:
619 : case GuitarBendType::SCOOP:
620 0 : return false;
621 2988 : default:
622 2988 : return true;
623 : }
624 : };
625 :
626 : // If this is a bend[dive], backtrack on the chain of previous dives[bends] as if they were ties
627 6137 : GuitarBend* bendOrDiveBack = isDive() ? startN->bendBack() : startN->diveBack();
628 7304 : while (bendOrDiveBack && bendOrDiveBack->isDive() != isDive() && isValidType(bendOrDiveBack->bendType())
629 7352 : && !bendOrDiveBack->overlappingBendOrDive()) {
630 48 : startN = bendOrDiveBack->startNote();
631 48 : bendOrDiveBack = isDive() ? startN->bendBack() : startN->diveBack();
632 : }
633 :
634 6137 : GuitarBend* precedingBend = isDive() ? startN->diveBack() : startN->bendBack();
635 6137 : if (precedingBend && precedingBend->isDive() == isDive() && isValidType(precedingBend->bendType())) {
636 2705 : return precedingBend;
637 : }
638 :
639 3432 : if (bendType() == GuitarBendType::PRE_DIVE || bendType() == GuitarBendType::PRE_BEND) {
640 563 : ChordRest* prevCR = Navigation::prevChordRest(startN->chord());
641 563 : if (prevCR && prevCR->isRest() && isDive()) {
642 77 : WhammyBar* whammyBar = findOverlappingWhammyBar(prevCR->tick(), tick2());
643 77 : if (whammyBar) {
644 0 : while (prevCR && prevCR->isRest() && prevCR->tick() > whammyBar->tick()) {
645 0 : prevCR = Navigation::prevChordRest(prevCR);
646 : }
647 : }
648 : }
649 :
650 563 : if (!prevCR || !prevCR->isChord()) {
651 307 : return nullptr;
652 : }
653 :
654 256 : Chord* prevChord = toChord(prevCR);
655 455 : for (Note* note : prevChord->notes()) {
656 268 : while (note->tieBack() && note->tieBack()->startNote()) {
657 3 : note = note->tieBack()->startNote();
658 : }
659 265 : GuitarBend* prevBend = note->bendBack();
660 265 : bool isValid = false;
661 235 : if (prevBend && prevBend->isDive() == isDive()
662 235 : && isValidType(prevBend->bendType())
663 500 : && prevBend->totBendAmountIncludingPrecedingBends() == bendAmountInQuarterTones()) {
664 150 : isValid = true;
665 : }
666 265 : if (isValid) {
667 150 : if (bendType() == GuitarBendType::PRE_BEND) {
668 84 : Note* sn = prevBend->startNote();
669 84 : isValid &= sn->fret() == startNote()->fret() && sn->string() == startNote()->string();
670 : }
671 150 : if (isValid) {
672 66 : return prevBend;
673 : }
674 : }
675 : }
676 : }
677 :
678 3059 : return nullptr;
679 : }
680 :
681 87 : GuitarBend* GuitarBend::findFollowingPreBendOrDive() const
682 : {
683 87 : Note* endN = endNote();
684 93 : while (endN->tieFor() && endN->tieFor()->endNote()) {
685 6 : endN = endN->tieFor()->endNote();
686 : }
687 :
688 87 : ChordRest* nextCR = Navigation::nextChordRest(endN->chord());
689 87 : if (isDive() && nextCR && nextCR->isRest()) {
690 0 : WhammyBar* whammyBar = findOverlappingWhammyBar(tick(), nextCR->endTick());
691 0 : if (whammyBar) {
692 0 : while (nextCR && nextCR->isRest() && nextCR->tick() < whammyBar->tick2()) {
693 0 : nextCR = Navigation::nextChordRest(nextCR);
694 : }
695 : }
696 : }
697 :
698 87 : if (!nextCR || !nextCR->isChord()) {
699 31 : return nullptr;
700 : }
701 :
702 56 : Chord* nextChord = toChord(nextCR);
703 112 : for (Note* note : nextChord->notes()) {
704 59 : while (note->tieFor() && note->tieFor()->endNote()) {
705 3 : note = note->tieFor()->endNote();
706 : }
707 56 : GuitarBend* bend = note->bendFor();
708 56 : bool isValid = false;
709 38 : if (bend && bend->isDive() == isDive()
710 38 : && (bend->bendType() == GuitarBendType::PRE_DIVE || bend->bendType() == GuitarBendType::PRE_BEND)
711 94 : && bend->bendAmountInQuarterTones() == totBendAmountIncludingPrecedingBends()) {
712 0 : isValid = true;
713 : }
714 56 : if (isValid) {
715 0 : if (bend->bendType() == GuitarBendType::PRE_BEND) {
716 0 : Note* startN = bend->startNote();
717 0 : isValid &= startN->fret() == startNote()->fret() && startN->string() == startNote()->string();
718 : }
719 0 : if (isValid) {
720 0 : return bend;
721 : }
722 : }
723 : }
724 :
725 56 : return nullptr;
726 : }
727 :
728 77 : WhammyBar* GuitarBend::findOverlappingWhammyBar(Fraction startTick, Fraction endTick) const
729 : {
730 77 : const auto& spanners = score()->spannerMap().findOverlapping(startTick.ticks(), endTick.ticks());
731 83 : for (const auto& item : spanners) {
732 6 : Spanner* spanner = item.value;
733 6 : if (spanner->isWhammyBar() && spanner->tick() < startTick && spanner->tick2() >= endTick) {
734 0 : Staff* spannerStaff = spanner->staff();
735 0 : Staff* thisStaff = staff();
736 0 : if (spannerStaff == thisStaff || spannerStaff->isLinked(thisStaff)) {
737 0 : return toWhammyBar(spanner);
738 : }
739 : }
740 : }
741 :
742 77 : return nullptr;
743 : }
744 :
745 1250 : void GuitarBend::updateHoldLine()
746 : {
747 1250 : Note* startOfHold = nullptr;
748 1250 : Note* endOfHold = nullptr;
749 :
750 1250 : bool isDipWithVibrato = bendType() == GuitarBendType::DIP && dipVibratoType() != VibratoType::NONE;
751 1337 : bool canHaveHoldLine = isDipWithVibrato || (staffType()->isTabStaff() && !isFullRelease() && bendType() != GuitarBendType::SCOOP
752 87 : && bendType() != GuitarBendType::DIP);
753 1250 : if (isDive()) {
754 468 : canHaveHoldLine &= startNote() && startNote() == startNote()->chord()->upNote();
755 : }
756 :
757 1250 : bool needsHoldLine = false;
758 1250 : if (canHaveHoldLine) {
759 87 : startOfHold = endNote();
760 87 : endOfHold = startOfHold;
761 :
762 87 : GuitarBend* followingPreDive = findFollowingPreBendOrDive();
763 87 : endOfHold = followingPreDive ? followingPreDive->startNote() : startOfHold;
764 :
765 87 : if (endOfHold == startOfHold) {
766 93 : while (endOfHold->tieFor() && endOfHold->tieFor()->endNote()) {
767 6 : endOfHold = endOfHold->tieFor()->endNote();
768 : }
769 : }
770 :
771 87 : if (isDive()) {
772 0 : GuitarBend* bendF = endOfHold->bendFor();
773 0 : while (bendF && !bendF->isDive() && !bendF->overlappingBendOrDive() && bendF->endNote() != endOfHold) {
774 0 : endOfHold = bendF->endNote();
775 0 : bendF = endOfHold->bendFor();
776 : }
777 : } else {
778 87 : GuitarBend* diveF = endOfHold->diveFor();
779 87 : while (diveF && !diveF->overlappingBendOrDive() && diveF->endNote() != endOfHold) {
780 0 : endOfHold = diveF->endNote();
781 0 : diveF = endOfHold->diveFor();
782 : }
783 : }
784 :
785 87 : if (showHoldLine() == GuitarBendShowHoldLine::AUTO) {
786 87 : needsHoldLine = endOfHold != startOfHold;
787 : } else {
788 0 : needsHoldLine = showHoldLine() == GuitarBendShowHoldLine::SHOW;
789 : }
790 87 : needsHoldLine |= isDipWithVibrato;
791 : }
792 :
793 1250 : if (!needsHoldLine) {
794 1244 : if (m_holdLine) {
795 1 : if (!m_holdLine->segmentsEmpty()) {
796 0 : m_holdLine->eraseSpannerSegments();
797 : }
798 1 : delete m_holdLine;
799 1 : m_holdLine = nullptr;
800 : }
801 1244 : return;
802 : }
803 :
804 6 : if (endOfHold == startOfHold) {
805 0 : Chord* guessedEndChord = startOfHold->chord()->next();
806 0 : if (guessedEndChord) {
807 0 : for (Note* note : guessedEndChord->notes()) {
808 0 : if (note->isPreBendOrDiveStart()) {
809 0 : endOfHold = note;
810 0 : break;
811 : }
812 : }
813 : }
814 : }
815 :
816 6 : if (!m_holdLine) {
817 0 : m_holdLine = new GuitarBendHold(this);
818 : }
819 :
820 6 : m_holdLine->setStartElement(startOfHold);
821 6 : m_holdLine->setEndElement(endOfHold);
822 6 : m_holdLine->setTick(startOfHold->tick());
823 6 : m_holdLine->setTick2(endOfHold->tick());
824 6 : m_holdLine->setTrack(track());
825 6 : m_holdLine->setTrack2(track());
826 6 : m_holdLine->setGenerated(true);
827 6 : m_holdLine->setOwnershipParent(this);
828 : }
829 :
830 164 : double GuitarBend::lineWidth() const
831 : {
832 164 : if (isDive()) {
833 0 : return (staffType() && staffType()->isTabStaff())
834 0 : ? style().styleAbsolute(Sid::guitarDiveLineWidthTab)
835 0 : : style().styleAbsolute(Sid::guitarDiveLineWidth);
836 : }
837 :
838 328 : return (staffType() && staffType()->isTabStaff())
839 328 : ? style().styleAbsolute(Sid::guitarBendLineWidthTab)
840 164 : : style().styleAbsolute(Sid::guitarBendLineWidth);
841 : }
842 :
843 : /****************************************
844 : * GuitarBendSegment
845 : * **************************************/
846 :
847 393 : GuitarBendSegment::GuitarBendSegment(GuitarBend* sp)
848 393 : : LineSegment(ElementType::GUITAR_BEND_SEGMENT, sp, ElementFlag::MOVABLE)
849 : {
850 393 : m_text = new GuitarBendText(this);
851 393 : setFlag(ElementFlag::ON_STAFF, true);
852 393 : }
853 :
854 4 : GuitarBendSegment::GuitarBendSegment(const GuitarBendSegment& s)
855 4 : : LineSegment(s)
856 : {
857 4 : m_vertexPointOff = s.m_vertexPointOff;
858 4 : m_text = new GuitarBendText(this);
859 4 : }
860 :
861 682 : GuitarBendSegment::~GuitarBendSegment()
862 : {
863 341 : delete m_text;
864 682 : }
865 :
866 0 : int GuitarBendSegment::gripsCount() const
867 : {
868 0 : switch (guitarBend()->bendType()) {
869 0 : case GuitarBendType::DIP:
870 : case GuitarBendType::SCOOP:
871 0 : return 0;
872 0 : case GuitarBendType::DIVE:
873 : case GuitarBendType::PRE_DIVE:
874 0 : return 3;
875 0 : default:
876 0 : return 4;
877 : }
878 : }
879 :
880 0 : std::vector<PointF> GuitarBendSegment::gripsPositions(const EditData&) const
881 : {
882 0 : int gripCount = gripsCount();
883 0 : if (gripCount == 0) {
884 0 : return {};
885 : }
886 :
887 0 : std::vector<PointF> grips(gripsCount());
888 0 : PointF pp(pagePos());
889 0 : grips[int(Grip::START)] = pp;
890 0 : grips[int(Grip::END)] = pos2() + pp;
891 0 : grips[int(Grip::MIDDLE)] = pos2() * .5 + pp;
892 :
893 0 : if (gripCount > 3) {
894 0 : grips[int(Grip::APERTURE)] = ldata()->vertexPoint() + vertexPointOff() + pp;
895 : }
896 :
897 0 : return grips;
898 0 : }
899 :
900 0 : void GuitarBendSegment::startDragGrip(EditData& ed)
901 : {
902 0 : ElementEditDataPtr eed = ed.getData(this);
903 0 : if (!eed) {
904 0 : return;
905 : }
906 0 : eed->pushProperty(Pid::BEND_VERTEX_OFF);
907 0 : LineSegment::startDragGrip(ed);
908 0 : }
909 :
910 0 : void GuitarBendSegment::dragGrip(EditData& ed)
911 : {
912 0 : PointF delta = ed.evtDelta;
913 :
914 0 : switch (ed.curGrip) {
915 0 : case Grip::START:
916 0 : setOffset(offset() + delta);
917 0 : setUserOff2(userOff2() - delta);
918 0 : setVertexPointOff(vertexPointOff() - delta);
919 0 : break;
920 0 : case Grip::END:
921 0 : setUserOff2(userOff2() + delta);
922 0 : break;
923 0 : case Grip::APERTURE:
924 0 : setVertexPointOff(vertexPointOff() + delta);
925 0 : break;
926 0 : case Grip::MIDDLE:
927 0 : setOffset(offset() + delta);
928 0 : break;
929 0 : default:
930 0 : UNREACHABLE;
931 : break;
932 : }
933 :
934 0 : triggerLayout();
935 0 : }
936 :
937 304 : PropertyValue GuitarBendSegment::getProperty(Pid id) const
938 : {
939 304 : switch (id) {
940 0 : case Pid::BEND_VERTEX_OFF:
941 0 : return vertexPointOff();
942 304 : default:
943 304 : return LineSegment::getProperty(id);
944 : }
945 : }
946 :
947 401 : bool GuitarBendSegment::setProperty(Pid propertyId, const PropertyValue& v)
948 : {
949 401 : switch (propertyId) {
950 0 : case Pid::BEND_VERTEX_OFF:
951 0 : setVertexPointOff(v.value<PointF>());
952 0 : break;
953 401 : default:
954 401 : return LineSegment::setProperty(propertyId, v);
955 : }
956 0 : triggerLayout();
957 0 : return true;
958 : }
959 :
960 304 : PropertyValue GuitarBendSegment::propertyDefault(Pid id) const
961 : {
962 304 : switch (id) {
963 0 : case Pid::BEND_VERTEX_OFF:
964 0 : return PointF();
965 304 : default:
966 304 : return LineSegment::propertyDefault(id);
967 : }
968 : }
969 :
970 1617 : EngravingObject* GuitarBendSegment::propertyDelegate(Pid id) const
971 : {
972 1617 : switch (id) {
973 0 : case Pid::DIRECTION:
974 : case Pid::BEND_SHOW_HOLD_LINE:
975 : case Pid::POSITION_LINKED_TO_MASTER:
976 : case Pid::APPEARANCE_LINKED_TO_MASTER:
977 : case Pid::GUITAR_DIVE_TAB_POS:
978 : case Pid::VIBRATO_LINE_TYPE:
979 0 : return guitarBend();
980 1617 : default:
981 1617 : return LineSegment::propertyDelegate(id);
982 : }
983 : }
984 :
985 0 : void GuitarBendSegment::reset()
986 : {
987 0 : undoResetProperty(Pid::DIRECTION);
988 0 : undoResetProperty(Pid::BEND_VERTEX_OFF);
989 0 : LineSegment::reset();
990 0 : }
991 :
992 164 : double GuitarBendSegment::lineWidth() const
993 : {
994 164 : return guitarBend()->lineWidth();
995 : }
996 :
997 8 : void GuitarBendSegment::scanElements(std::function<void(EngravingItem*)> func)
998 : {
999 8 : func(m_text);
1000 8 : LineSegment::scanElements(func);
1001 8 : }
1002 :
1003 304 : bool GuitarBendSegment::isUserModified() const
1004 : {
1005 304 : bool modified = !vertexPointOff().isNull() || (m_text && m_text->isUserModified());
1006 304 : return modified || LineSegment::isUserModified();
1007 : }
1008 :
1009 0 : Color GuitarBend::curColor(const rendering::PaintOptions& opt) const
1010 : {
1011 0 : if (!opt.isPrinting && MScore::warnGuitarBends) {
1012 0 : if (m_isInvalid) {
1013 0 : return selected() ? configuration()->criticalSelectedColor() : configuration()->criticalColor();
1014 : }
1015 :
1016 0 : if (m_isBorderlineUnplayable) {
1017 0 : return selected() ? configuration()->warningSelectedColor() : configuration()->warningColor();
1018 : }
1019 : }
1020 :
1021 0 : return EngravingItem::curColor(opt);
1022 : }
1023 :
1024 0 : void GuitarBend::adaptBendsFromTabToStandardStaff(const Staff* staff)
1025 : {
1026 : // On tabs, bends force end notes to be invisible. When switching to
1027 : // normal staff we need to turn all the end notes visible again.
1028 :
1029 0 : auto processBends = [](Chord* chord) {
1030 0 : for (Note* note : chord->notes()) {
1031 0 : GuitarBend* bend = note->bendFor();
1032 0 : if (!bend) {
1033 0 : continue;
1034 : }
1035 0 : if (!bend->startNote()->overrideBendVisibilityRules()) {
1036 0 : bend->startNote()->setVisible(true);
1037 : }
1038 0 : if (!bend->endNote()->overrideBendVisibilityRules()) {
1039 0 : bend->endNote()->setVisible(true);
1040 : }
1041 : }
1042 0 : };
1043 :
1044 0 : staff_idx_t staffIdx = staff->idx();
1045 0 : track_idx_t startTrack = staff2track(staffIdx);
1046 0 : track_idx_t endTrack = startTrack + VOICES;
1047 0 : for (Segment* segment = staff->score()->firstSegment(SegmentType::ChordRest); segment; segment = segment->next1()) {
1048 0 : if (!segment->isChordRestType()) {
1049 0 : continue;
1050 : }
1051 0 : for (track_idx_t track = startTrack; track < endTrack; ++track) {
1052 0 : EngravingItem* item = segment->element(track);
1053 0 : if (!item || !item->isChord()) {
1054 0 : continue;
1055 : }
1056 0 : Chord* chord = toChord(item);
1057 0 : processBends(chord);
1058 0 : for (Chord* grace : chord->graceNotes()) {
1059 0 : processBends(grace);
1060 : }
1061 : }
1062 : }
1063 0 : }
1064 :
1065 0 : GuitarBendType GuitarBend::bendTypeFromActionIcon(ActionIconType actionIconType)
1066 : {
1067 0 : switch (actionIconType) {
1068 0 : case ActionIconType::STANDARD_BEND: return GuitarBendType::BEND;
1069 0 : case ActionIconType::PRE_BEND: return GuitarBendType::PRE_BEND;
1070 0 : case ActionIconType::GRACE_NOTE_BEND: return GuitarBendType::GRACE_NOTE_BEND;
1071 0 : case ActionIconType::SLIGHT_BEND: return GuitarBendType::SLIGHT_BEND;
1072 0 : case ActionIconType::DIVE: return GuitarBendType::DIVE;
1073 0 : case ActionIconType::PRE_DIVE: return GuitarBendType::PRE_DIVE;
1074 0 : case ActionIconType::DIP: return GuitarBendType::DIP;
1075 0 : case ActionIconType::SCOOP: return GuitarBendType::SCOOP;
1076 0 : default:
1077 0 : return GuitarBendType::BEND;
1078 : }
1079 : }
1080 :
1081 395 : void GuitarBend::setBendType(GuitarBendType t)
1082 : {
1083 395 : m_bendType = t;
1084 :
1085 395 : resetProperty(Pid::GUITAR_BEND_AMOUNT);
1086 395 : resetProperty(Pid::BEND_START_TIME_FACTOR);
1087 395 : resetProperty(Pid::BEND_END_TIME_FACTOR);
1088 395 : resetProperty(Pid::BEND_TARGET_TIME_FACTOR);
1089 395 : }
1090 :
1091 4 : void GuitarBend::setTargetTimeFactor(float f)
1092 : {
1093 4 : IF_ASSERT_FAILED(muse::RealIsEqualOrLess(f, endTimeFactor())) {
1094 0 : return;
1095 : }
1096 :
1097 4 : m_targetTimeFactor = f;
1098 : }
1099 :
1100 : /****************************************
1101 : * GuitarBendHold
1102 : * **************************************/
1103 :
1104 3 : GuitarBendHold::GuitarBendHold(GuitarBend* parent)
1105 3 : : SLine(ElementType::GUITAR_BEND_HOLD, parent, ElementFlag::MOVABLE)
1106 : {
1107 3 : resetProperty(Pid::LINE_STYLE);
1108 3 : }
1109 :
1110 0 : GuitarBendHold::GuitarBendHold(const GuitarBendHold& h)
1111 0 : : SLine(h)
1112 : {
1113 0 : }
1114 :
1115 2 : LineSegment* GuitarBendHold::createLineSegment()
1116 : {
1117 2 : GuitarBendHoldSegment* seg = new GuitarBendHoldSegment(this);
1118 2 : seg->setTrack(track());
1119 2 : seg->setColor(lineColor());
1120 2 : return seg;
1121 : }
1122 :
1123 3 : PropertyValue GuitarBendHold::propertyDefault(Pid id) const
1124 : {
1125 3 : switch (id) {
1126 3 : case Pid::LINE_STYLE:
1127 3 : return LineType::DASHED;
1128 0 : default:
1129 0 : return SLine::propertyDefault(id);
1130 : }
1131 : }
1132 :
1133 0 : Note* GuitarBendHold::startNote() const
1134 : {
1135 0 : EngravingItem* startEl = startElement();
1136 0 : assert(startEl && startEl->isNote());
1137 0 : return toNote(startEl);
1138 : }
1139 :
1140 6 : Note* GuitarBendHold::endNote() const
1141 : {
1142 6 : EngravingItem* endEl = endElement();
1143 6 : assert(endEl && endEl->isNote());
1144 6 : return toNote(endEl);
1145 : }
1146 :
1147 6 : double GuitarBendHold::lineWidth() const
1148 : {
1149 6 : return style().styleAbsolute(parent() && toGuitarBend(parent())->isDive() ? Sid::guitarDiveLineWidthTab : Sid::guitarBendLineWidthTab);
1150 : }
1151 :
1152 : /****************************************
1153 : * GuitarBendHoldSegment
1154 : * **************************************/
1155 :
1156 2 : GuitarBendHoldSegment::GuitarBendHoldSegment(GuitarBendHold* sp)
1157 2 : : LineSegment(ElementType::GUITAR_BEND_HOLD_SEGMENT, sp, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
1158 : {
1159 2 : setFlag(ElementFlag::ON_STAFF, true);
1160 2 : }
1161 :
1162 0 : void GuitarBendHoldSegment::dragGrip(EditData& ed)
1163 : {
1164 0 : PointF delta = ed.evtDelta;
1165 :
1166 0 : switch (ed.curGrip) {
1167 0 : case Grip::START:
1168 0 : setOffset(offset() + delta);
1169 0 : setUserOff2(userOff2() - delta);
1170 0 : break;
1171 0 : case Grip::END:
1172 0 : setUserOff2(userOff2() + delta);
1173 0 : break;
1174 0 : case Grip::MIDDLE:
1175 0 : setOffset(offset() + delta);
1176 0 : break;
1177 0 : default:
1178 0 : break;
1179 : }
1180 :
1181 0 : triggerLayout();
1182 0 : }
1183 :
1184 : /****************************************
1185 : * GuitarBendText
1186 : * **************************************/
1187 :
1188 : static const ElementStyle guitarBendStyle {};
1189 :
1190 397 : GuitarBendText::GuitarBendText(GuitarBendSegment* parent)
1191 397 : : TextBase(ElementType::GUITAR_BEND_TEXT, parent, TextStyleType::BEND)
1192 : {
1193 397 : initElementStyle(&guitarBendStyle);
1194 397 : }
1195 : } // namespace mu::engraving
|