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 "tuplet.h"
24 :
25 : #include "../editing/elementeditdata.h"
26 : #include "style/textstyle.h"
27 :
28 : #include "beam.h"
29 : #include "chord.h"
30 : #include "engravingitem.h"
31 : #include "factory.h"
32 : #include "measure.h"
33 : #include "note.h"
34 : #include "rest.h"
35 : #include "score.h"
36 : #include "text.h"
37 :
38 : #include "log.h"
39 :
40 : using namespace mu;
41 : using namespace mu::engraving;
42 :
43 : namespace mu::engraving {
44 : //---------------------------------------------------------
45 : // tupletStyle
46 : //---------------------------------------------------------
47 :
48 : static const ElementStyle tupletStyle {
49 : { Sid::tupletDirection, Pid::DIRECTION },
50 : { Sid::tupletNumberType, Pid::NUMBER_TYPE },
51 : { Sid::tupletBracketType, Pid::BRACKET_TYPE },
52 : { Sid::tupletBracketWidth, Pid::LINE_WIDTH },
53 : { Sid::tupletFontFace, Pid::FONT_FACE },
54 : { Sid::tupletFontSize, Pid::FONT_SIZE },
55 : { Sid::tupletFontStyle, Pid::FONT_STYLE },
56 : { Sid::tupletAlign, Pid::ALIGN },
57 : { Sid::tupletPosition, Pid::POSITION },
58 : { Sid::tupletMinDistance, Pid::MIN_DISTANCE },
59 : { Sid::tupletFontSpatiumDependent, Pid::SIZE_SPATIUM_DEPENDENT },
60 : };
61 :
62 : //---------------------------------------------------------
63 : // Tuplet
64 : //---------------------------------------------------------
65 :
66 1041 : Tuplet::Tuplet(DummyParentOr<Measure> parent)
67 1041 : : DurationElement(ElementType::TUPLET, parent)
68 : {
69 1041 : m_direction = DirectionV::AUTO;
70 1041 : m_numberType = TupletNumberType::SHOW_NUMBER;
71 1041 : m_bracketType = TupletBracketType::AUTO_BRACKET;
72 1041 : m_ratio = Fraction(1, 1);
73 1041 : m_number = 0;
74 1041 : m_hasBracket = false;
75 1041 : m_isUp = true;
76 1041 : initElementStyle(&tupletStyle);
77 1041 : }
78 :
79 52 : Tuplet::Tuplet(const Tuplet& t)
80 52 : : DurationElement(t)
81 : {
82 52 : m_tick = t.m_tick;
83 52 : m_hasBracket = t.m_hasBracket;
84 52 : m_ratio = t.m_ratio;
85 52 : m_baseLen = t.m_baseLen;
86 52 : m_direction = t.m_direction;
87 52 : m_numberType = t.m_numberType;
88 52 : m_bracketType = t.m_bracketType;
89 52 : m_bracketWidth = t.m_bracketWidth;
90 :
91 52 : m_isUp = t.m_isUp;
92 :
93 52 : m_p1 = t.m_p1;
94 52 : m_p2 = t.m_p2;
95 52 : m_userP1 = t.m_userP1;
96 52 : m_userP2 = t.m_userP2;
97 :
98 : // recreated on layout
99 52 : m_number = 0;
100 52 : }
101 :
102 : //---------------------------------------------------------
103 : // ~Tuplet
104 : //---------------------------------------------------------
105 :
106 1230 : Tuplet::~Tuplet()
107 : {
108 615 : m_beingDestructed = true;
109 :
110 713 : for (DurationElement* de : m_allElements) {
111 98 : assert(de->tuplet() == this);
112 98 : de->setTuplet(nullptr);
113 : }
114 615 : delete m_number;
115 1230 : }
116 :
117 96 : void Tuplet::setOwnershipParent(DummyParentOr<Measure> parent)
118 : {
119 96 : EngravingItem::setOwnershipParent(parent);
120 96 : }
121 :
122 : //---------------------------------------------------------
123 : // setSelected
124 : //---------------------------------------------------------
125 :
126 522 : void Tuplet::setSelected(bool f)
127 : {
128 522 : EngravingItem::setSelected(f);
129 522 : if (m_number) {
130 479 : m_number->setSelected(f);
131 : }
132 522 : }
133 :
134 : //---------------------------------------------------------
135 : // setVisible
136 : //---------------------------------------------------------
137 :
138 0 : void Tuplet::setVisible(bool f)
139 : {
140 0 : EngravingItem::setVisible(f);
141 0 : if (m_number) {
142 0 : m_number->setVisible(f);
143 : }
144 0 : }
145 :
146 : //---------------------------------------------------------
147 : // setColor
148 : //---------------------------------------------------------
149 :
150 3 : void Tuplet::setColor(const Color& col)
151 : {
152 3 : EngravingItem::setColor(col);
153 3 : if (m_number) {
154 0 : m_number->setColor(col);
155 : }
156 3 : }
157 :
158 48678 : EngravingObject* Tuplet::propertyDelegate(Pid id) const
159 : {
160 48678 : switch (id) {
161 20593 : case Pid::FONT_FACE:
162 : case Pid::FONT_STYLE:
163 : case Pid::FONT_SIZE:
164 : case Pid::ALIGN:
165 : case Pid::POSITION:
166 : case Pid::SIZE_SPATIUM_DEPENDENT:
167 20593 : return m_number;
168 28085 : default:
169 28085 : return nullptr;
170 : }
171 : }
172 :
173 : //---------------------------------------------------------
174 : // rtick
175 : //---------------------------------------------------------
176 :
177 521 : Fraction Tuplet::rtick() const
178 : {
179 521 : return tick() - measure()->tick();
180 : }
181 :
182 : //---------------------------------------------------------
183 : // resetNumberProperty
184 : // reset number properties to default values
185 : // Set FONT_ITALIC to true, because for tuplets number should be italic
186 : //---------------------------------------------------------
187 :
188 392 : void Tuplet::resetNumberProperty()
189 : {
190 392 : resetNumberProperty(m_number);
191 392 : }
192 :
193 778 : void Tuplet::resetNumberProperty(Text* number)
194 : {
195 5446 : for (auto p : { Pid::FONT_FACE, Pid::FONT_STYLE, Pid::FONT_SIZE, Pid::ALIGN, Pid::POSITION, Pid::SIZE_SPATIUM_DEPENDENT }) {
196 4668 : number->resetProperty(p);
197 : }
198 778 : }
199 :
200 : //---------------------------------------------------------
201 : // calcHasBracket
202 : //---------------------------------------------------------
203 :
204 2029 : bool Tuplet::calcHasBracket(const DurationElement* cr1, const DurationElement* cr2) const
205 : {
206 2029 : if (m_bracketType != TupletBracketType::AUTO_BRACKET) {
207 180 : return m_bracketType != TupletBracketType::SHOW_NO_BRACKET;
208 : }
209 1849 : if (cr1 == cr2) { // Degenerate tuplet
210 27 : return false;
211 : }
212 1822 : if (!cr1->isChord() || !cr2->isChord()) {
213 631 : return true;
214 : }
215 1191 : const Chord* c1 = toChord(cr1);
216 1191 : const Chord* c2 = toChord(cr2);
217 :
218 1191 : Beam const* beamStart = c1->beam();
219 1191 : Beam const* beamEnd = c2->beam();
220 1191 : if (!beamStart || !beamEnd || beamStart != beamEnd) {
221 278 : return true;
222 : }
223 913 : bool tupletStartsBeam = beamStart->elements().front() == c1;
224 913 : bool tupletEndsBeam = beamEnd->elements().back() == c2;
225 913 : bool headSide = isUp() != (c1->up() || c2->up());
226 913 : bool isCross = c1->vStaffIdx() != c2->vStaffIdx();
227 913 : if (tupletStartsBeam && tupletEndsBeam && (!headSide || isCross)) {
228 621 : return false;
229 : }
230 :
231 292 : int beamCount = -1;
232 1162 : for (DurationElement* e : m_currentElements) {
233 899 : if (e->isTuplet() || e->isRest()) {
234 29 : return true;
235 873 : } else if (e->isChordRest()) {
236 873 : ChordRest* cr = toChordRest(e);
237 873 : if (cr->beam() == 0) {
238 0 : return true;
239 : }
240 873 : if (beamCount == -1) {
241 282 : beamCount = cr->beams();
242 591 : } else if (beamCount != cr->beams()) {
243 3 : return true;
244 : }
245 : }
246 : }
247 263 : if (beamCount < 1) {
248 0 : return true;
249 : }
250 :
251 263 : bool startChordBreaks32 = false;
252 263 : bool startChordBreaks64 = false;
253 263 : Chord* prevStartChord = c1->prev();
254 263 : if (prevStartChord) {
255 257 : beamStart->calcBeamBreaks(c1, prevStartChord, beamCount - 1, startChordBreaks32, startChordBreaks64);
256 : }
257 263 : bool startChordDefinesTuplet = startChordBreaks32 || startChordBreaks64 || tupletStartsBeam;
258 263 : if (prevStartChord) {
259 257 : startChordDefinesTuplet = startChordDefinesTuplet || prevStartChord->beams() < beamCount;
260 : }
261 :
262 263 : bool endChordBreaks32 = false;
263 263 : bool endChordBreaks64 = false;
264 263 : Chord* nextEndChord = c2->next();
265 263 : if (nextEndChord) {
266 144 : beamEnd->calcBeamBreaks(nextEndChord, c2, beamCount - 1, endChordBreaks32, endChordBreaks64);
267 : }
268 263 : bool endChordDefinesTuplet = endChordBreaks32 || endChordBreaks64 || tupletEndsBeam;
269 263 : if (nextEndChord) {
270 144 : endChordDefinesTuplet = endChordDefinesTuplet || nextEndChord->beams() < beamCount;
271 : }
272 :
273 263 : if (startChordDefinesTuplet && endChordDefinesTuplet && (!headSide || isCross)) {
274 251 : return false;
275 : }
276 :
277 12 : return true;
278 : }
279 :
280 : //---------------------------------------------------------
281 : // scanElements
282 : //---------------------------------------------------------
283 :
284 1451 : void Tuplet::scanElements(std::function<void(EngravingItem*)> func)
285 : {
286 1451 : if (m_number) {
287 917 : m_number->scanElements(func);
288 : }
289 :
290 1451 : func(this);
291 1451 : }
292 :
293 : //---------------------------------------------------------
294 : // add
295 : //---------------------------------------------------------
296 :
297 4082 : void Tuplet::add(EngravingItem* e)
298 : {
299 4082 : switch (e->type()) {
300 4082 : case ElementType::CHORD:
301 : case ElementType::REST:
302 : case ElementType::TUPLET: {
303 4082 : DurationElement* de = toDurationElement(e);
304 4082 : IF_ASSERT_FAILED(!contains(de)) {
305 0 : return;
306 : }
307 4082 : de->setTuplet(this);
308 :
309 4082 : bool found = false;
310 4082 : Fraction tick = de->rtick();
311 4082 : if (tick != Fraction(-1, 1)) {
312 10823 : for (unsigned int i = 0; i < m_currentElements.size(); ++i) {
313 6901 : if (m_currentElements[i]->rtick() > tick) {
314 160 : m_currentElements.insert(m_currentElements.begin() + i, de);
315 160 : found = true;
316 160 : break;
317 : }
318 : }
319 : }
320 4082 : if (!found) {
321 3922 : m_currentElements.push_back(de);
322 : }
323 : }
324 4082 : break;
325 :
326 0 : default:
327 0 : LOGD("Tuplet::add() unknown element");
328 0 : return;
329 : }
330 :
331 4082 : e->added();
332 : }
333 :
334 : //---------------------------------------------------------
335 : // remove
336 : //---------------------------------------------------------
337 :
338 517 : void Tuplet::remove(EngravingItem* e)
339 : {
340 517 : switch (e->type()) {
341 : // case ElementType::TEXT:
342 : // if (e == _number)
343 : // _number = 0;
344 : // break;
345 517 : case ElementType::CHORD:
346 : case ElementType::REST:
347 : case ElementType::TUPLET: {
348 517 : auto i = std::find(m_currentElements.begin(), m_currentElements.end(), toDurationElement(e));
349 517 : if (i == m_currentElements.end()) {
350 0 : LOGD("Tuplet::remove: cannot find element <%s>", e->typeName());
351 0 : LOGD(" elements %zu", m_currentElements.size());
352 : } else {
353 517 : m_currentElements.erase(i);
354 517 : e->removed();
355 : }
356 : }
357 517 : break;
358 0 : default:
359 0 : LOGD("Tuplet::remove: unknown element");
360 0 : break;
361 : }
362 517 : }
363 :
364 5657 : void Tuplet::addDurationElement(DurationElement* de)
365 : {
366 5657 : assert(!m_beingDestructed);
367 5657 : m_allElements.insert(de);
368 5657 : }
369 :
370 4115 : void Tuplet::removeDurationElement(DurationElement* de)
371 : {
372 4115 : if (m_beingDestructed) {
373 98 : return;
374 : }
375 :
376 4017 : m_allElements.erase(de);
377 : }
378 :
379 : //---------------------------------------------------------
380 : // isEditable
381 : //---------------------------------------------------------
382 :
383 0 : bool Tuplet::isEditable() const
384 : {
385 0 : return m_hasBracket;
386 : }
387 :
388 : //---------------------------------------------------------
389 : // startDragGrip
390 : //---------------------------------------------------------
391 :
392 0 : void Tuplet::startDragGrip(EditData& ed)
393 : {
394 0 : DurationElement::startDragGrip(ed);
395 0 : ElementEditDataPtr eed = ed.getData(this);
396 :
397 0 : eed->pushProperty(Pid::P1);
398 0 : eed->pushProperty(Pid::P2);
399 0 : }
400 :
401 : //---------------------------------------------------------
402 : // dragGrip
403 : //---------------------------------------------------------
404 :
405 0 : void Tuplet::dragGrip(EditData& ed)
406 : {
407 0 : if (ed.curGrip == Grip::START) {
408 0 : m_userP1 += ed.delta;
409 0 : } else if (ed.curGrip == Grip::END) {
410 0 : m_userP2 += ed.delta;
411 0 : } else if (ed.curGrip == Grip::MIDDLE) {
412 0 : m_userP1 += ed.delta;
413 0 : m_userP2 += ed.delta;
414 : } else {
415 0 : UNREACHABLE;
416 : return;
417 : }
418 :
419 0 : setGenerated(false);
420 0 : triggerLayout();
421 : }
422 :
423 : //---------------------------------------------------------
424 : // gripsPositions
425 : //---------------------------------------------------------
426 :
427 0 : int Tuplet::gripsCount() const
428 : {
429 0 : return m_hasBracket ? 3 : 0;
430 : }
431 :
432 0 : std::vector<PointF> Tuplet::gripsPositions(const EditData&) const
433 : {
434 0 : IF_ASSERT_FAILED(gripsCount() != 0) {
435 0 : return std::vector<PointF>();
436 : }
437 :
438 0 : const PointF pp(pagePos());
439 0 : PointF left = pp + m_p1;
440 0 : PointF right = pp + m_p2;
441 0 : PointF middle = 0.5 * (left + right);
442 :
443 0 : return { left, right, middle };
444 : }
445 :
446 : //---------------------------------------------------------
447 : // reset
448 : //---------------------------------------------------------
449 :
450 0 : void Tuplet::reset()
451 : {
452 0 : undoChangeProperty(Pid::P1, PointF());
453 0 : undoChangeProperty(Pid::P2, PointF());
454 0 : EngravingItem::reset();
455 0 : }
456 :
457 : //---------------------------------------------------------
458 : // dump
459 : //---------------------------------------------------------
460 :
461 0 : void Tuplet::dump() const
462 : {
463 0 : EngravingItem::dump();
464 0 : LOGD() << "ratio: " << m_ratio.toString();
465 0 : }
466 :
467 : //---------------------------------------------------------
468 : // setTrack
469 : //---------------------------------------------------------
470 :
471 2326 : void Tuplet::setTrack(track_idx_t val)
472 : {
473 2326 : if (tuplet()) {
474 266 : tuplet()->setTrack(val);
475 : }
476 2326 : if (m_number) {
477 694 : m_number->setTrack(val);
478 : }
479 2326 : EngravingItem::setTrack(val);
480 2326 : }
481 :
482 : //---------------------------------------------------------
483 : // tickGreater
484 : //---------------------------------------------------------
485 :
486 611 : static bool tickGreater(const DurationElement* a, const DurationElement* b)
487 : {
488 611 : return a->tick() < b->tick();
489 : }
490 :
491 : //---------------------------------------------------------
492 : // sortElements
493 : //---------------------------------------------------------
494 :
495 83 : void Tuplet::sortElements()
496 : {
497 83 : std::sort(m_currentElements.begin(), m_currentElements.end(), tickGreater);
498 83 : }
499 :
500 : //---------------------------------------------------------
501 : // cross
502 : //---------------------------------------------------------
503 :
504 5930 : bool Tuplet::cross() const
505 : {
506 25716 : for (DurationElement* de : m_currentElements) {
507 19909 : if (!de) {
508 0 : continue;
509 19909 : } else if (de->isChordRest()) {
510 17951 : if (toChordRest(de)->staffMove()) {
511 123 : return true;
512 : }
513 1958 : } else if (de->isTuplet()) {
514 1958 : if (toTuplet(de)->cross()) {
515 0 : return true;
516 : }
517 : }
518 : }
519 5807 : return false;
520 : }
521 :
522 : //---------------------------------------------------------
523 : // vStaffIdx
524 : /// Staff index for layout based on the first ChordRest
525 : //---------------------------------------------------------
526 :
527 4270 : staff_idx_t Tuplet::vStaffIdx() const
528 : {
529 4270 : if (elements().empty()) {
530 148 : return muse::nidx;
531 : }
532 :
533 4122 : const DurationElement* cr = elements().front();
534 4122 : if (!cr) {
535 0 : return muse::nidx;
536 : }
537 :
538 4324 : while (cr->isTuplet()) {
539 202 : const Tuplet* t = toTuplet(cr);
540 202 : if (t->elements().empty()) {
541 0 : return muse::nidx;
542 : }
543 202 : cr = t->elements().front();
544 : }
545 :
546 4122 : return cr->vStaffIdx();
547 : }
548 :
549 : //---------------------------------------------------------
550 : // elementsDuration
551 : /// Get the sum of the element fraction in the tuplet,
552 : /// even if the tuplet is not complete yet
553 : //---------------------------------------------------------
554 :
555 57 : Fraction Tuplet::elementsDuration()
556 : {
557 57 : Fraction f;
558 174 : for (DurationElement* el : m_currentElements) {
559 117 : f += el->ticks();
560 : }
561 57 : return f;
562 : }
563 :
564 : //---------------------------------------------------------
565 : // getProperty
566 : //---------------------------------------------------------
567 :
568 8031 : PropertyValue Tuplet::getProperty(Pid propertyId) const
569 : {
570 8031 : if (EngravingObject* delegate = propertyDelegate(propertyId)) {
571 0 : return delegate->getProperty(propertyId);
572 : }
573 8031 : switch (propertyId) {
574 159 : case Pid::DIRECTION:
575 159 : return PropertyValue::fromValue<DirectionV>(m_direction);
576 13 : case Pid::NUMBER_TYPE:
577 13 : return int(m_numberType);
578 11 : case Pid::BRACKET_TYPE:
579 11 : return int(m_bracketType);
580 1 : case Pid::LINE_WIDTH:
581 1 : return m_bracketWidth;
582 599 : case Pid::NORMAL_NOTES:
583 599 : return m_ratio.denominator();
584 599 : case Pid::ACTUAL_NOTES:
585 599 : return m_ratio.numerator();
586 599 : case Pid::P1:
587 599 : return m_userP1;
588 599 : case Pid::P2:
589 599 : return m_userP2;
590 5451 : default:
591 5451 : break;
592 : }
593 5451 : return DurationElement::getProperty(propertyId);
594 : }
595 :
596 : //---------------------------------------------------------
597 : // setProperty
598 : //---------------------------------------------------------
599 :
600 12099 : bool Tuplet::setProperty(Pid propertyId, const PropertyValue& v)
601 : {
602 12099 : if (EngravingObject* delegate = propertyDelegate(propertyId)) {
603 36 : return delegate->setProperty(propertyId, v);
604 : }
605 12063 : switch (propertyId) {
606 1187 : case Pid::DIRECTION:
607 1187 : setDirection(v.value<DirectionV>());
608 1187 : break;
609 1324 : case Pid::NUMBER_TYPE:
610 1324 : setNumberType(TupletNumberType(v.toInt()));
611 1324 : break;
612 1067 : case Pid::BRACKET_TYPE:
613 1067 : setBracketType(TupletBracketType(v.toInt()));
614 1067 : break;
615 1054 : case Pid::LINE_WIDTH:
616 1054 : setBracketWidth(v.value<Spatium>());
617 1054 : break;
618 27 : case Pid::NORMAL_NOTES:
619 27 : m_ratio.setDenominator(v.toInt());
620 27 : break;
621 27 : case Pid::ACTUAL_NOTES:
622 27 : m_ratio.setNumerator(v.toInt());
623 27 : break;
624 0 : case Pid::P1:
625 0 : m_userP1 = v.value<PointF>();
626 0 : break;
627 0 : case Pid::P2:
628 0 : m_userP2 = v.value<PointF>();
629 0 : break;
630 23 : case Pid::TICK:
631 23 : setTick(v.value<Fraction>());
632 23 : break;
633 7354 : default:
634 7354 : return DurationElement::setProperty(propertyId, v);
635 : }
636 4709 : if (!m_currentElements.empty()) {
637 47 : m_currentElements.front()->triggerLayout();
638 47 : m_currentElements.back()->triggerLayout();
639 : }
640 4709 : return true;
641 : }
642 :
643 : //---------------------------------------------------------
644 : // propertyDefault
645 : //---------------------------------------------------------
646 :
647 13508 : PropertyValue Tuplet::propertyDefault(Pid id) const
648 : {
649 13508 : switch (id) {
650 0 : case Pid::TEXT_STYLE:
651 0 : return TextStyleType::TUPLET;
652 0 : case Pid::SYSTEM_FLAG:
653 0 : return false;
654 589 : case Pid::TEXT:
655 1178 : return String();
656 1198 : case Pid::NORMAL_NOTES:
657 : case Pid::ACTUAL_NOTES:
658 1198 : return 0;
659 1198 : case Pid::P1:
660 : case Pid::P2:
661 2396 : return PointF();
662 778 : case Pid::POSITION:
663 778 : return style().styleV(Sid::tupletPosition);
664 794 : case Pid::ALIGN:
665 794 : return style().styleV(Sid::tupletAlign);
666 866 : case Pid::FONT_FACE:
667 866 : return style().styleV(Sid::tupletFontFace);
668 866 : case Pid::FONT_SIZE:
669 866 : return style().styleV(Sid::tupletFontSize);
670 866 : case Pid::FONT_STYLE:
671 866 : return style().styleV(Sid::tupletFontStyle);
672 778 : case Pid::SIZE_SPATIUM_DEPENDENT:
673 778 : return style().styleV(Sid::tupletFontSpatiumDependent);
674 5575 : default:
675 5575 : break;
676 : }
677 :
678 : // TODO: what does this do? Why TextStyleType::DEFAULT?
679 88186 : for (const auto& spp : *textStyle(TextStyleType::DEFAULT)) {
680 83210 : if (spp.pid == id) {
681 599 : return styleValue(id, spp.sid);
682 : }
683 : }
684 :
685 4976 : return DurationElement::propertyDefault(id);
686 : }
687 :
688 : //---------------------------------------------------------
689 : // sanitizeTuplet
690 : /// Check validity of tuplets and coherence between duration
691 : /// and baselength. Needed for importing old files due to a bug
692 : /// in the released version for corner-case tuplets.
693 : /// See issue #136406 and Pull request #2881
694 : //---------------------------------------------------------
695 :
696 28 : void Tuplet::sanitizeTuplet()
697 : {
698 28 : if (ratio().numerator() == ratio().reduced().numerator()) { // return if the ratio is an irreducible fraction
699 16 : return;
700 : }
701 12 : Fraction baseLenDuration = (Fraction(ratio().denominator(), 1) * baseLen().fraction()).reduced();
702 :
703 : // Due to a bug present in 2.1 (and before), a tuplet with non-reduced ratio could be
704 : // in a corrupted state (mismatch between duration and base length).
705 : // A tentative will now be made to retrieve the correct duration by summing up all the
706 : // durations of the elements constituting the tuplet. This does not work for
707 : // not-completely filled tuplets, such as tuplets in voices > 0 with
708 : // gaps (for example, a tuplet in second voice with a deleted chordrest element)
709 :
710 12 : Fraction testDuration(0, 1);
711 89 : for (DurationElement* de : elements()) {
712 77 : if (!de) {
713 0 : continue;
714 : }
715 77 : Fraction elementDuration(0, 1);
716 77 : if (de->isTuplet()) {
717 1 : Tuplet* t = toTuplet(de);
718 1 : t->sanitizeTuplet();
719 1 : elementDuration = t->ticks();
720 : } else {
721 76 : elementDuration = de->ticks();
722 : }
723 77 : testDuration += elementDuration;
724 : }
725 12 : testDuration = testDuration / ratio();
726 12 : testDuration.reduce();
727 12 : if (elements().back()->endTick() - elements().front()->tick() > testDuration) {
728 0 : return; // this tuplet has missing elements; do not sanitize
729 : }
730 12 : if (!(testDuration == baseLenDuration && baseLenDuration == ticks())) {
731 10 : Fraction f = testDuration * Fraction(1, ratio().denominator());
732 10 : f.reduce();
733 10 : Fraction fbl(1, f.denominator());
734 10 : if (TDuration::isValid(fbl)) {
735 10 : setTicks(testDuration);
736 10 : setBaseLen(fbl);
737 10 : LOGD("Tuplet %p sanitized duration %d/%d baseLen %d/%d", this,
738 : testDuration.numerator(), testDuration.denominator(),
739 : 1, fbl.denominator());
740 : } else {
741 0 : LOGD("Impossible to sanitize the tuplet");
742 : }
743 : }
744 : }
745 :
746 : //---------------------------------------------------------
747 : // addMissingElement
748 : // Add a rest with the given start and end ticks.
749 : // Should only be called from Tuplet::addMissingElements().
750 : // Needed for importing files that saved incomplete tuplets.
751 : //---------------------------------------------------------
752 :
753 0 : Fraction Tuplet::addMissingElement(const Fraction& startTick, const Fraction& endTick)
754 : {
755 0 : Fraction f = (endTick - startTick) * ratio();
756 0 : TDuration d = TDuration(f, true);
757 0 : if (!d.isValid()) {
758 0 : LOGD("Tuplet::addMissingElement(): invalid duration: %d/%d", f.numerator(), f.denominator());
759 0 : return Fraction::fromTicks(0);
760 : }
761 0 : f = d.fraction();
762 0 : Segment* segment = measure()->getSegment(SegmentType::ChordRest, startTick);
763 0 : Rest* rest = Factory::createRest(segment);
764 0 : rest->setDurationType(d);
765 0 : rest->setTicks(f);
766 0 : rest->setTrack(track());
767 0 : rest->setVisible(false);
768 :
769 0 : segment->add(rest);
770 0 : add(rest);
771 0 : return f;
772 : }
773 :
774 : //---------------------------------------------------------
775 : // addMissingElements
776 : // Make this tuplet complete by filling in holes where
777 : // there ought to be rests. Needed for importing files
778 : // that saved incomplete tuplets.
779 : //---------------------------------------------------------
780 :
781 27 : void Tuplet::addMissingElements()
782 : {
783 27 : if (tuplet()) {
784 27 : return; // do not correct nested tuplets
785 : }
786 20 : if (voice() == 0) {
787 20 : return; // nothing to do for tuplets in voice 1
788 : }
789 :
790 0 : Fraction missingElementsDuration = ticks() * ratio() - elementsDuration();
791 0 : if (missingElementsDuration.isZero()) {
792 0 : return;
793 : }
794 :
795 : // first, fill in any holes in the middle of the tuplet
796 0 : Fraction expectedTick = elements().front()->tick();
797 :
798 0 : const std::vector<DurationElement*> elementsCopy = elements(); // mofified during loop
799 0 : for (const DurationElement* de : elementsCopy) {
800 0 : if (!de) {
801 0 : continue;
802 : }
803 0 : if (de->tick() != expectedTick) {
804 0 : missingElementsDuration -= addMissingElement(expectedTick, de->tick());
805 0 : if (missingElementsDuration.isZero()) {
806 0 : return;
807 : }
808 : }
809 0 : expectedTick += de->actualTicks();
810 : }
811 :
812 : // calculate the tick where we would expect a tuplet of this duration to start
813 : // TODO: check:
814 0 : expectedTick = elements().front()->tick() - Fraction::fromTicks(elements().front()->tick().ticks() % ticks().ticks());
815 0 : if (expectedTick != elements().front()->tick()) {
816 : // try to fill a hole at the beginning of the tuplet
817 0 : Fraction firstAvailableTick = measure()->tick();
818 0 : Segment* segment = measure()->findSegment(SegmentType::ChordRest, elements().front()->tick());
819 0 : ChordRest* prevChordRest = segment && segment->prev() ? segment->prev()->nextChordRest(track(), true) : nullptr;
820 0 : if (prevChordRest && prevChordRest->measure() == measure()) {
821 0 : firstAvailableTick = prevChordRest->endTick();
822 : }
823 0 : if (firstAvailableTick != elements().front()->tick()) {
824 0 : Fraction f = missingElementsDuration / ratio();
825 0 : Fraction ticksRequired = f;
826 0 : Fraction endTick = elements().front()->tick();
827 0 : Fraction startTick = std::max(firstAvailableTick, endTick - ticksRequired);
828 0 : if (expectedTick > startTick) {
829 0 : startTick = expectedTick;
830 : }
831 0 : missingElementsDuration -= addMissingElement(startTick, endTick);
832 0 : if (missingElementsDuration.isZero()) {
833 0 : return;
834 : }
835 : }
836 : }
837 : // now fill a hole at the end of the tuplet
838 0 : Fraction startTick = elements().back()->endTick();
839 0 : Fraction endTick = elements().front()->tick() + ticks();
840 : // just to be safe, find the next ChordRest in the track, and adjust endTick if necessary
841 0 : Segment* segment = measure()->findSegment(SegmentType::ChordRest, elements().back()->tick());
842 0 : ChordRest* nextChordRest = segment && segment->next() ? segment->next()->nextChordRest(track(), false) : nullptr;
843 0 : if (nextChordRest && nextChordRest->tick() < endTick) {
844 0 : endTick = nextChordRest->tick();
845 : }
846 0 : missingElementsDuration -= addMissingElement(startTick, endTick);
847 0 : if (!missingElementsDuration.isZero()) {
848 0 : LOGD("Tuplet::addMissingElements(): still missing duration of %d/%d",
849 : missingElementsDuration.numerator(), missingElementsDuration.denominator());
850 : }
851 0 : }
852 :
853 11 : int Tuplet::computeTupletDenominator(int numerator, Fraction totalDuration)
854 : {
855 11 : if (numerator <= 0) {
856 0 : return 0;
857 : }
858 11 : Fraction noteDuration = totalDuration / numerator;
859 11 : Fraction baseLen(1, 1);
860 11 : Fraction ratio = (totalDuration / baseLen).reduced();
861 46 : while (ratio.denominator() != 1 || baseLen / 2 >= noteDuration) {
862 35 : baseLen /= 2;
863 35 : ratio = (totalDuration / baseLen).reduced();
864 : }
865 11 : return ratio.numerator();
866 : }
867 :
868 0 : EngravingItem* Tuplet::nextElement()
869 : {
870 0 : ChordRest* firstElement = toChordRest(elements().front());
871 0 : if (firstElement->isChord()) {
872 0 : Chord* chord = toChord(firstElement);
873 0 : return chord->firstGraceOrNote();
874 : }
875 0 : return firstElement;
876 : }
877 :
878 0 : EngravingItem* Tuplet::prevElement()
879 : {
880 0 : ChordRest* firstElement = toChordRest(elements().front());
881 0 : staff_idx_t staffId = firstElement->staffIdx();
882 0 : EngravingItem* prevItem = firstElement->segment()->prevElement(staffId);
883 0 : return prevItem;
884 : }
885 : } // namespace mu::engraving
|