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 "beam.h"
24 :
25 : #include <cmath>
26 : #include <algorithm>
27 :
28 : #include "containers.h"
29 : #include "realfn.h"
30 :
31 : #include "../editing/elementeditdata.h"
32 :
33 : #include "actionicon.h"
34 : #include "chord.h"
35 : #include "groups.h"
36 : #include "measure.h"
37 : #include "rootitem.h"
38 : #include "score.h"
39 : #include "segment.h"
40 : #include "staff.h"
41 : #include "system.h"
42 : #include "tremolotwochord.h"
43 : #include "tuplet.h"
44 :
45 : #include "log.h"
46 :
47 : using namespace mu;
48 : using namespace muse;
49 : using namespace mu::engraving;
50 :
51 : namespace mu::engraving {
52 : static const ElementStyle beamStyle {
53 : { Sid::beamNoSlope, Pid::BEAM_NO_SLOPE },
54 : };
55 :
56 : //---------------------------------------------------------
57 : // Beam
58 : //---------------------------------------------------------
59 :
60 2872 : Beam::Beam(Score* parent)
61 2872 : : BeamBase(ElementType::BEAM, parent->dummy())
62 : {
63 2872 : initElementStyle(&beamStyle);
64 2872 : resetProperty(Pid::BEAM_CROSS_STAFF_MOVE);
65 2872 : }
66 :
67 648031 : System* Beam::system() const
68 : {
69 648031 : if (m_elements.empty()) {
70 58 : return nullptr;
71 : }
72 647973 : Measure* measure = m_elements.front()->measure();
73 647973 : return measure ? measure->system() : nullptr;
74 : }
75 :
76 639253 : EngravingItem* Beam::layoutParent() const
77 : {
78 639253 : return system();
79 : }
80 :
81 4 : EngravingItem* Beam::accessibleParentItem() const
82 : {
83 : // The system a beam is drawn on does not own it and cannot list it, so in the
84 : // accessibility tree the beam stays where it is parked: on the dummy.
85 4 : return score()->dummy()->rootItem();
86 : }
87 :
88 : //---------------------------------------------------------
89 : // Beam
90 : //---------------------------------------------------------
91 :
92 18 : Beam::Beam(const Beam& b)
93 18 : : BeamBase(b)
94 : {
95 18 : m_elements = b.m_elements;
96 18 : m_growLeft = b.m_growLeft;
97 18 : m_growRight = b.m_growRight;
98 18 : m_beamDist = b.m_beamDist;
99 18 : m_fragments.reserve(b.m_fragments.size());
100 36 : for (const BeamFragment* f : b.m_fragments) {
101 18 : m_fragments.push_back(new BeamFragment(*f));
102 : }
103 18 : m_minCRMove = b.m_minCRMove;
104 18 : m_maxCRMove = b.m_maxCRMove;
105 18 : m_isGrace = b.m_isGrace;
106 18 : m_cross = b.m_cross;
107 18 : m_fullCross = b.m_fullCross;
108 18 : m_slope = b.m_slope;
109 18 : }
110 :
111 : //---------------------------------------------------------
112 : // Beam
113 : //---------------------------------------------------------
114 :
115 4904 : Beam::~Beam()
116 : {
117 : //
118 : // delete all references from chords
119 : //
120 2452 : for (ChordRest* cr : m_elements) {
121 0 : cr->setBeam(nullptr);
122 : }
123 :
124 2452 : clearBeamSegments();
125 :
126 2452 : muse::DeleteAll(m_fragments);
127 2452 : m_fragments.clear();
128 4904 : }
129 :
130 : //---------------------------------------------------------
131 : // add
132 : //---------------------------------------------------------
133 :
134 9619 : void Beam::add(EngravingItem* e)
135 : {
136 9619 : if (e->isChordRest()) {
137 9619 : addChordRest(toChordRest(e));
138 9619 : e->added();
139 : }
140 9619 : }
141 :
142 : //---------------------------------------------------------
143 : // remove
144 : //---------------------------------------------------------
145 :
146 1541 : void Beam::remove(EngravingItem* e)
147 : {
148 1541 : if (e->isChordRest()) {
149 1541 : removeChordRest(toChordRest(e));
150 1541 : e->removed();
151 : }
152 1541 : }
153 :
154 : //---------------------------------------------------------
155 : // addChordRest
156 : //---------------------------------------------------------
157 :
158 9619 : void Beam::addChordRest(ChordRest* a)
159 : {
160 9619 : a->setBeam(this);
161 9619 : if (!muse::contains(m_elements, a)) {
162 : //
163 : // insert element in same order as it appears
164 : // in the score
165 : //
166 9619 : if (a->segment() && !m_elements.empty()) {
167 23282 : for (size_t i = 0; i < m_elements.size(); ++i) {
168 16626 : Segment* s = m_elements[i]->segment();
169 16626 : if ((s->tick() > a->segment()->tick())
170 16626 : || ((s->tick() == a->segment()->tick()) && (a->segment()->next(SegmentType::ChordRest) == s))
171 : ) {
172 1 : m_elements.insert(m_elements.begin() + i, a);
173 1 : return;
174 : }
175 : }
176 : }
177 9618 : m_elements.push_back(a);
178 : }
179 : }
180 :
181 : //---------------------------------------------------------
182 : // removeChordRest
183 : //---------------------------------------------------------
184 :
185 1541 : void Beam::removeChordRest(ChordRest* a)
186 : {
187 1541 : if (!muse::remove(m_elements, a)) {
188 0 : LOGD("Beam::remove(): cannot find ChordRest");
189 : }
190 1541 : a->setBeam(0);
191 1541 : }
192 :
193 2664 : const Chord* Beam::findChordWithCustomStemDirection() const
194 : {
195 11941 : for (const ChordRest* rest: elements()) {
196 9293 : if (!rest->isChord()) {
197 29 : continue;
198 : }
199 :
200 9264 : const Chord* chord = toChord(rest);
201 9264 : if (chord->stemDirection() != DirectionV::AUTO) {
202 16 : return chord;
203 : }
204 : }
205 :
206 2648 : return nullptr;
207 : }
208 :
209 64 : const BeamSegment* Beam::topLevelSegmentForElement(const ChordRest* element) const
210 : {
211 64 : const std::vector<BeamSegment*>& segments = beamSegments();
212 64 : size_t segmentsSize = segments.size();
213 :
214 64 : IF_ASSERT_FAILED(segmentsSize > 0) {
215 0 : return nullptr;
216 : }
217 :
218 64 : const BeamSegment* curSegment = segments[0];
219 64 : if (segmentsSize == 1) {
220 16 : return curSegment;
221 : }
222 :
223 144 : for (const BeamSegment* segment : segments) {
224 96 : if (segment->level <= curSegment->level) {
225 48 : continue;
226 : }
227 48 : Fraction elementTick = element->tick();
228 48 : if (segment->startTick <= elementTick && segment->endTick >= elementTick) {
229 48 : curSegment = segment;
230 : }
231 : }
232 :
233 48 : return curSegment;
234 : }
235 :
236 : //---------------------------------------------------------
237 : // move
238 : //---------------------------------------------------------
239 :
240 0 : void Beam::move(const PointF& offset)
241 : {
242 0 : EngravingItem::move(offset);
243 0 : for (BeamSegment* bs : m_beamSegments) {
244 0 : bs->line.translate(offset);
245 : }
246 0 : }
247 :
248 74294 : void Beam::calcBeamBreaks(const ChordRest* cr, const ChordRest* prevCr, int level, bool& isBroken16, bool& isBroken32) const
249 : {
250 74294 : BeamMode beamMode = cr->beamMode();
251 74294 : if (cr->isRest() && (beamMode == BeamMode::MID || beamMode == BeamMode::BEGIN16 || beamMode == BeamMode::BEGIN32)) {
252 : // when a rest has beamMode MID we can just ignore it entirely and allow any beams to continue through
253 251 : switch (beamMode) {
254 251 : case BeamMode::MID:
255 251 : isBroken16 = isBroken32 = false;
256 251 : break;
257 0 : case BeamMode::BEGIN16:
258 0 : isBroken16 = level > 0;
259 0 : isBroken32 = false;
260 0 : break;
261 0 : case BeamMode::BEGIN32:
262 0 : isBroken16 = false;
263 0 : isBroken32 = level > 1;
264 0 : break;
265 0 : default:
266 : // should be unreachable
267 0 : assert(false);
268 : }
269 251 : return;
270 : }
271 : // get default beam mode -- based on time signature preferences
272 74043 : const Groups& group = cr->staff()->group(cr->measure()->tick());
273 74043 : BeamMode defaultBeamMode = group.baseBeamMode(cr, prevCr);
274 :
275 74043 : bool isManuallyBroken16 = level >= 1 && beamMode == BeamMode::BEGIN16;
276 74043 : bool isManuallyBroken32 = level >= 2 && beamMode == BeamMode::BEGIN32;
277 74043 : bool isDefaultBroken16 = beamMode == BeamMode::AUTO && level >= 1 && defaultBeamMode == BeamMode::BEGIN16;
278 74043 : bool isDefaultBroken32 = beamMode == BeamMode::AUTO && level >= 2 && defaultBeamMode == BeamMode::BEGIN32;
279 :
280 74043 : isBroken16 = isManuallyBroken16 || isDefaultBroken16;
281 74043 : isBroken32 = isManuallyBroken32 || isDefaultBroken32;
282 :
283 : // deal with beam-embedded triplets by breaking beams as if they are their underlying durations
284 : // note that we use max(hooks, 1) here because otherwise we'd end up breaking the main (level 0) beam for
285 : // tuplets that take up non-beamed amounts of space (eg. 16th note quintuplets)
286 : // a tuplet's total duration needn't be a single note value, so truncate it
287 74043 : if (level > 0 && prevCr && cr->beamMode() == BeamMode::AUTO) {
288 25053 : if (cr->tuplet() && cr->tuplet() != prevCr->tuplet()) {
289 : // this cr starts a tuplet
290 1580 : int beams = std::max(TDuration(cr->tuplet()->ticks(), /*truncate*/ true).hooks(), 1);
291 1580 : if (beams <= level) {
292 1430 : isBroken16 = level == 1;
293 1430 : isBroken32 = level >= 2;
294 : }
295 23473 : } else if (prevCr->tuplet() && prevCr->tuplet() != cr->tuplet()) {
296 : // this is a non-tuplet cr that is first after a tuplet
297 45 : int beams = std::max(TDuration(prevCr->tuplet()->ticks(), /*truncate*/ true).hooks(), 1);
298 45 : if (beams <= level) {
299 45 : isBroken16 = level == 1;
300 45 : isBroken32 = level >= 2;
301 : }
302 : }
303 : }
304 : }
305 :
306 : //---------------------------------------------------------
307 : // spatiumChanged
308 : //---------------------------------------------------------
309 :
310 2 : void Beam::spatiumChanged(double oldValue, double newValue)
311 : {
312 2 : int idx = directionIdx();
313 2 : if (userModified()) {
314 0 : double diff = newValue / oldValue;
315 0 : for (BeamFragment* f : m_fragments) {
316 0 : f->py1[idx] = f->py1[idx] * diff;
317 0 : f->py2[idx] = f->py2[idx] * diff;
318 : }
319 : }
320 2 : }
321 :
322 : //---------------------------------------------------------
323 : // BeamEditData
324 : //---------------------------------------------------------
325 :
326 : class BeamEditData : public ElementEditData
327 : {
328 0 : OBJECT_ALLOCATOR(engraving, BeamEditData)
329 : public:
330 : int editFragment;
331 0 : virtual EditDataType type() override { return EditDataType::BeamEditData; }
332 : };
333 :
334 : //---------------------------------------------------------
335 : // dragGrip
336 : //---------------------------------------------------------
337 :
338 0 : void Beam::dragGrip(EditData& ed)
339 : {
340 0 : int idx = directionIdx();
341 0 : double dy = ed.delta.y();
342 0 : BeamEditData* bed = static_cast<BeamEditData*>(ed.getData(this).get());
343 0 : BeamFragment* f = m_fragments[bed->editFragment];
344 0 : double y1 = f->py1[idx];
345 0 : double y2 = f->py2[idx];
346 :
347 0 : if (ed.curGrip == Grip::MIDDLE || noSlope()) {
348 0 : y1 += dy;
349 0 : y2 += dy;
350 0 : } else if (ed.curGrip == Grip::START) {
351 0 : y1 += dy;
352 0 : } else if (ed.curGrip == Grip::END) {
353 0 : y2 += dy;
354 : } else {
355 0 : UNREACHABLE;
356 : return;
357 : }
358 :
359 0 : double _spatium = spatium();
360 : // Because of the logic in Beam::setProperty(),
361 : // changing Pid::BEAM_POS only has an effect if Pid::USER_MODIFIED is true.
362 0 : undoChangeProperty(Pid::USER_MODIFIED, true);
363 0 : undoChangeProperty(Pid::BEAM_POS, PairF(y1 / _spatium, y2 / _spatium));
364 0 : undoChangeProperty(Pid::GENERATED, false);
365 :
366 0 : triggerLayout();
367 : }
368 :
369 : //---------------------------------------------------------
370 : // gripsPositions
371 : //---------------------------------------------------------
372 :
373 0 : std::vector<PointF> Beam::gripsPositions(const EditData& ed) const
374 : {
375 0 : int idx = directionIdx();
376 0 : BeamEditData* bed = static_cast<BeamEditData*>(ed.getData(this).get());
377 0 : BeamFragment* f = m_fragments[bed->editFragment];
378 :
379 0 : ChordRest* c1 = nullptr;
380 0 : ChordRest* c2 = nullptr;
381 0 : size_t n = m_elements.size();
382 :
383 0 : if (n == 0) {
384 0 : return std::vector<PointF>();
385 : }
386 :
387 0 : for (size_t i = 0; i < n; ++i) {
388 0 : if (m_elements[i]->isChordRest()) {
389 0 : c1 = toChordRest(m_elements[i]);
390 0 : break;
391 : }
392 : }
393 0 : if (!c1) { // no chord/rest found, no need to check again below
394 0 : return {}; // just ignore the requested operation
395 : }
396 0 : for (int i = static_cast<int>(n) - 1; i >= 0; --i) {
397 0 : if (m_elements[i]->isChordRest()) {
398 0 : c2 = toChordRest(m_elements[i]);
399 0 : break;
400 : }
401 : }
402 0 : if (!c2) { // no chord/rest found, no need to check again below
403 0 : return {}; // just ignore the requested operation
404 : }
405 :
406 0 : int y = pagePos().y();
407 0 : double beamStartX = m_startAnchor.x() + (system() ? system()->x() : 0);
408 0 : double beamEndX = m_endAnchor.x() + (system() ? system()->x() : 0);
409 0 : double middleX = (beamStartX + beamEndX) / 2;
410 0 : double middleY = (f->py1[idx] + y + f->py2[idx] + y) / 2;
411 :
412 : return {
413 0 : PointF(beamStartX, f->py1[idx] + y),
414 0 : PointF(beamEndX, f->py2[idx] + y),
415 : PointF(middleX, middleY)
416 0 : };
417 : }
418 :
419 : //---------------------------------------------------------
420 : // setBeamDirection
421 : //---------------------------------------------------------
422 :
423 783 : void Beam::setDirection(DirectionV d)
424 : {
425 783 : if (direction() == d || m_cross) {
426 95 : return;
427 : }
428 :
429 688 : doSetDirection(d);
430 :
431 688 : if (d != DirectionV::AUTO) {
432 687 : setUp(d == DirectionV::UP);
433 : }
434 :
435 700 : for (ChordRest* e : elements()) {
436 12 : if (e->isChord()) {
437 12 : toChord(e)->undoChangeProperty(Pid::STEM_DIRECTION, d);
438 : }
439 : }
440 : }
441 :
442 2 : void Beam::setAsFeathered(const bool slower)
443 : {
444 2 : if (slower) {
445 1 : undoChangeProperty(Pid::GROW_LEFT, 1.0);
446 1 : undoChangeProperty(Pid::GROW_RIGHT, 0.0);
447 : } else {
448 1 : undoChangeProperty(Pid::GROW_LEFT, 0.0);
449 1 : undoChangeProperty(Pid::GROW_RIGHT, 1.0);
450 : }
451 2 : }
452 :
453 : //---------------------------------------------------------
454 : // reset
455 : //---------------------------------------------------------
456 :
457 0 : void Beam::reset()
458 : {
459 0 : if (!RealIsEqual(growLeft(), 1.0)) {
460 0 : undoChangeProperty(Pid::GROW_LEFT, 1.0);
461 : }
462 0 : if (!RealIsEqual(growRight(), 1.0)) {
463 0 : undoChangeProperty(Pid::GROW_RIGHT, 1.0);
464 : }
465 0 : if (userModified()) {
466 0 : undoChangeProperty(Pid::BEAM_POS, PropertyValue::fromValue(beamPos()));
467 0 : undoChangeProperty(Pid::USER_MODIFIED, false);
468 : }
469 0 : undoChangeProperty(Pid::STEM_DIRECTION, DirectionV::AUTO);
470 0 : undoResetProperty(Pid::BEAM_NO_SLOPE);
471 0 : undoResetProperty(Pid::BEAM_CROSS_STAFF_MOVE);
472 0 : undoChangeProperty(Pid::GENERATED, true);
473 0 : }
474 :
475 : //---------------------------------------------------------
476 : // startEdit
477 : //---------------------------------------------------------
478 :
479 0 : void Beam::startEdit(EditData& ed)
480 : {
481 0 : initBeamEditData(ed);
482 0 : }
483 :
484 : //---------------------------------------------------------
485 : // triggerLayout
486 : //---------------------------------------------------------
487 :
488 9522 : void Beam::triggerLayout() const
489 : {
490 9522 : if (!m_elements.empty()) {
491 69 : m_elements.front()->triggerLayout();
492 69 : m_elements.back()->triggerLayout();
493 : }
494 9522 : }
495 :
496 : //---------------------------------------------------------
497 : // acceptDrop
498 : //---------------------------------------------------------
499 :
500 0 : bool Beam::acceptDrop(EditData& data) const
501 : {
502 0 : EngravingItem* e = data.dropElement;
503 :
504 0 : if (e->isActionIcon()) {
505 0 : ActionIconType type = toActionIcon(e)->actionType();
506 : return type == ActionIconType::BEAM_FEATHERED_DECELERATE
507 0 : || type == ActionIconType::BEAM_FEATHERED_ACCELERATE;
508 : }
509 :
510 0 : return false;
511 : }
512 :
513 : //---------------------------------------------------------
514 : // drop
515 : //---------------------------------------------------------
516 :
517 0 : EngravingItem* Beam::drop(Transaction&, EditData& data)
518 : {
519 0 : if (!data.dropElement->isActionIcon()) {
520 0 : return nullptr;
521 : }
522 :
523 0 : ActionIcon* e = toActionIcon(data.dropElement);
524 :
525 0 : if (e->actionType() == ActionIconType::BEAM_FEATHERED_DECELERATE) {
526 0 : setAsFeathered(true /*slower*/);
527 0 : } else if (e->actionType() == ActionIconType::BEAM_FEATHERED_ACCELERATE) {
528 0 : setAsFeathered(false /*slower*/);
529 : }
530 :
531 0 : return nullptr;
532 : }
533 :
534 : //---------------------------------------------------------
535 : // beamPos
536 : //---------------------------------------------------------
537 :
538 0 : PairF Beam::beamPos() const
539 : {
540 0 : if (m_fragments.empty()) {
541 0 : return PairF(0.0, 0.0);
542 : }
543 0 : BeamFragment* f = m_fragments.back();
544 0 : int idx = directionIdx();
545 0 : double _spatium = spatium();
546 0 : return PairF(f->py1[idx] / _spatium, f->py2[idx] / _spatium);
547 : }
548 :
549 : //---------------------------------------------------------
550 : // setBeamPos
551 : //---------------------------------------------------------
552 :
553 0 : void Beam::setBeamPos(const PairF& bp)
554 : {
555 0 : if (m_fragments.empty()) {
556 0 : m_fragments.push_back(new BeamFragment);
557 : }
558 0 : BeamFragment* f = m_fragments.back();
559 0 : int idx = directionIdx();
560 0 : setUserModified(true);
561 0 : setGenerated(false);
562 :
563 0 : double _spatium = spatium();
564 0 : if (noSlope()) {
565 0 : f->py1[idx] = f->py2[idx] = (bp.first + bp.second) * 0.5 * _spatium;
566 : } else {
567 0 : f->py1[idx] = bp.first * _spatium;
568 0 : f->py2[idx] = bp.second * _spatium;
569 : }
570 0 : }
571 :
572 2877 : void Beam::setNoSlope(bool b)
573 : {
574 2877 : m_noSlope = b;
575 :
576 : // Make flat if usermodified
577 2877 : if (m_noSlope) {
578 9 : int idx = directionIdx();
579 9 : if (userModified()) {
580 0 : BeamFragment* f = m_fragments.back();
581 0 : f->py1[idx] = f->py2[idx] = (f->py1[idx] + f->py2[idx]) * 0.5;
582 : }
583 : }
584 2877 : }
585 :
586 8778 : void Beam::computeAndSetSlope()
587 : {
588 8778 : double xDiff = m_endAnchor.x() - m_startAnchor.x();
589 8778 : double yDiff = m_endAnchor.y() - m_startAnchor.y();
590 8778 : if (std::abs(xDiff) < 0.5 * spatium()) {
591 : // Temporary safeguard: a beam this short is invalid, and exists only as a temporary state,
592 : // so don't try to compute the slope as it will be wrong. Needs a better solution in future.
593 2809 : setSlope(0.0);
594 : } else {
595 5969 : setSlope(yDiff / xDiff);
596 : }
597 8778 : }
598 :
599 : //---------------------------------------------------------
600 : // getProperty
601 : //---------------------------------------------------------
602 :
603 21257 : PropertyValue Beam::getProperty(Pid propertyId) const
604 : {
605 21257 : switch (propertyId) {
606 1624 : case Pid::GROW_LEFT: return growLeft();
607 1624 : case Pid::GROW_RIGHT: return growRight();
608 0 : case Pid::BEAM_POS: return PropertyValue::fromValue(beamPos());
609 0 : case Pid::BEAM_NO_SLOPE: return noSlope();
610 3240 : case Pid::POSITION_LINKED_TO_MASTER:
611 : case Pid::APPEARANCE_LINKED_TO_MASTER:
612 13834 : for (ChordRest* chordRest : elements()) {
613 10594 : bool linked = chordRest->getProperty(propertyId).toBool();
614 10594 : if (!linked) {
615 0 : return false;
616 : }
617 : }
618 3240 : return true;
619 14769 : default:
620 14769 : return BeamBase::getProperty(propertyId);
621 : }
622 : }
623 :
624 : //---------------------------------------------------------
625 : // setProperty
626 : //---------------------------------------------------------
627 :
628 6150 : bool Beam::setProperty(Pid propertyId, const PropertyValue& v)
629 : {
630 6150 : switch (propertyId) {
631 1 : case Pid::GROW_LEFT:
632 1 : setGrowLeft(v.toDouble());
633 1 : break;
634 1 : case Pid::GROW_RIGHT:
635 1 : setGrowRight(v.toDouble());
636 1 : break;
637 0 : case Pid::BEAM_POS:
638 0 : if (userModified()) {
639 0 : setBeamPos(v.value<PairF>());
640 : }
641 0 : break;
642 2877 : case Pid::BEAM_NO_SLOPE:
643 2877 : setNoSlope(v.toBool());
644 2877 : break;
645 0 : case Pid::POSITION_LINKED_TO_MASTER:
646 : case Pid::APPEARANCE_LINKED_TO_MASTER:
647 0 : if (v.toBool() == true) {
648 0 : for (ChordRest* chordRest : elements()) {
649 : // when re-linking, re-link all the chords
650 0 : chordRest->setProperty(propertyId, v);
651 : }
652 0 : resetProperty(Pid::STEM_DIRECTION);
653 0 : break;
654 : }
655 : // fall through
656 : default:
657 3271 : if (!BeamBase::setProperty(propertyId, v)) {
658 0 : return false;
659 : }
660 3271 : break;
661 : }
662 6150 : triggerLayout();
663 6150 : setGenerated(false);
664 6150 : return true;
665 : }
666 :
667 : //---------------------------------------------------------
668 : // propertyDefault
669 : //---------------------------------------------------------
670 :
671 23932 : PropertyValue Beam::propertyDefault(Pid id) const
672 : {
673 23932 : switch (id) {
674 1620 : case Pid::GROW_LEFT: return 1.0;
675 1620 : case Pid::GROW_RIGHT: return 1.0;
676 0 : case Pid::BEAM_POS: return PropertyValue::fromValue(beamPos());
677 20692 : default: return BeamBase::propertyDefault(id);
678 : }
679 : }
680 :
681 : //---------------------------------------------------------
682 : // addSkyline
683 : // add beam shape to skyline
684 : //---------------------------------------------------------
685 :
686 4211 : void Beam::addSkyline(Skyline& sk)
687 : {
688 4211 : if (m_beamSegments.empty() || !addToSkyline()) {
689 8 : return;
690 : }
691 :
692 : // Only add the outer segment, no need to add the inner one
693 4203 : sk.add(m_beamSegments.front()->shape());
694 : }
695 :
696 : //---------------------------------------------------------
697 : // tick
698 : //---------------------------------------------------------
699 :
700 27769 : Fraction Beam::tick() const
701 : {
702 27769 : return m_elements.empty() ? Fraction(0, 1) : m_elements.front()->segment()->tick();
703 : }
704 :
705 : //---------------------------------------------------------
706 : // rtick
707 : //---------------------------------------------------------
708 :
709 0 : Fraction Beam::rtick() const
710 : {
711 0 : return m_elements.empty() ? Fraction(0, 1) : m_elements.front()->segment()->rtick();
712 : }
713 :
714 : //---------------------------------------------------------
715 : // ticks
716 : // calculate the ticks of all chords and rests connected by the beam
717 : //---------------------------------------------------------
718 :
719 4346 : Fraction Beam::ticks() const
720 : {
721 4346 : Fraction ticks = Fraction(0, 1);
722 19200 : for (ChordRest* cr : m_elements) {
723 14854 : ticks += cr->actualTicks();
724 : }
725 4346 : return ticks;
726 : }
727 :
728 : //---------------------------------------------------------
729 : // actionIconTypeForBeamMode
730 : //---------------------------------------------------------
731 :
732 0 : ActionIconType Beam::actionIconTypeForBeamMode(BeamMode mode)
733 : {
734 0 : switch (mode) {
735 0 : case BeamMode::AUTO:
736 0 : return ActionIconType::BEAM_AUTO;
737 0 : case BeamMode::NONE:
738 0 : return ActionIconType::BEAM_NONE;
739 0 : case BeamMode::BEGIN:
740 0 : return ActionIconType::BEAM_BREAK_LEFT;
741 0 : case BeamMode::BEGIN16:
742 0 : return ActionIconType::BEAM_BREAK_INNER_8TH;
743 0 : case BeamMode::BEGIN32:
744 0 : return ActionIconType::BEAM_BREAK_INNER_16TH;
745 0 : case BeamMode::MID:
746 0 : return ActionIconType::BEAM_JOIN;
747 0 : default:
748 0 : break;
749 : }
750 0 : return ActionIconType::UNDEFINED;
751 : }
752 :
753 : //---------------------------------------------------------
754 : // drag
755 : //---------------------------------------------------------
756 :
757 0 : RectF Beam::drag(EditData& ed)
758 : {
759 0 : int idx = directionIdx();
760 0 : double dy = ed.pos.y() - ed.lastPos.y();
761 0 : BeamEditData* bed = static_cast<BeamEditData*>(ed.getData(this).get());
762 0 : BeamFragment* f = m_fragments[bed->editFragment];
763 :
764 0 : double y1 = f->py1[idx];
765 0 : double y2 = f->py2[idx];
766 :
767 0 : y1 += dy;
768 0 : y2 += dy;
769 :
770 0 : double _spatium = spatium();
771 : // Because of the logic in Beam::setProperty(),
772 : // changing Pid::BEAM_POS only has an effect if Pid::USER_MODIFIED is true.
773 0 : undoChangeProperty(Pid::USER_MODIFIED, true);
774 0 : undoChangeProperty(Pid::BEAM_POS, PairF(y1 / _spatium, y2 / _spatium));
775 0 : undoChangeProperty(Pid::GENERATED, false);
776 :
777 0 : triggerLayout();
778 :
779 0 : return canvasBoundingRect();
780 : }
781 :
782 : //---------------------------------------------------------
783 : // isMovable
784 : //---------------------------------------------------------
785 0 : bool Beam::isMovable() const
786 : {
787 0 : return true;
788 : }
789 :
790 : //---------------------------------------------------------
791 : // initBeamEditData
792 : //---------------------------------------------------------
793 0 : void Beam::initBeamEditData(EditData& ed)
794 : {
795 0 : std::shared_ptr<BeamEditData> bed = std::make_shared<BeamEditData>();
796 0 : bed->e = this;
797 0 : bed->editFragment = 0;
798 0 : ed.addData(bed);
799 0 : }
800 :
801 : //---------------------------------------------------------
802 : // startDrag
803 : //---------------------------------------------------------
804 0 : void Beam::startDrag(EditData& editData)
805 : {
806 0 : initBeamEditData(editData);
807 0 : }
808 : }
809 :
810 : //---------------------------------------------------------
811 : // containsChord
812 : //---------------------------------------------------------
813 9272 : bool Beam::hasAllRests()
814 : {
815 9272 : for (ChordRest* cr : m_elements) {
816 9272 : if (cr && cr->isChord()) {
817 9272 : return false;
818 : }
819 : }
820 0 : return true;
821 : }
822 :
823 11230 : void Beam::clearBeamSegments()
824 : {
825 41103 : for (ChordRest* chordRest : m_elements) {
826 29873 : chordRest->setBeamlet(nullptr);
827 : }
828 :
829 11230 : BeamBase::clearBeamSegments();
830 11230 : }
831 :
832 : //-------------------------------------------------------
833 : // BEAM SEGMENT CLASS
834 : //-------------------------------------------------------
835 :
836 13295 : BeamSegment::BeamSegment(EngravingItem* b)
837 13295 : : parentElement(b)
838 : {
839 13295 : DO_ASSERT(parentElement->isType(ElementType::BEAM) || parentElement->isType(ElementType::TREMOLO_TWOCHORD));
840 13295 : }
841 :
842 18197 : Shape BeamSegment::shape() const
843 : {
844 18197 : Shape shape;
845 18197 : PointF startPoint = line.p1();
846 18197 : PointF endPoint = line.p2();
847 18197 : double _beamWidth = parentElement->isBeam()
848 18197 : ? item_cast<const Beam*>(parentElement)->m_beamWidth
849 0 : : item_cast<const TremoloTwoChord*>(parentElement)->ldata()->beamWidth;
850 : // This is the case of right-beamlets
851 18197 : if (startPoint.x() > endPoint.x()) {
852 1245 : std::swap(startPoint, endPoint);
853 : }
854 18197 : double beamHorizontalLength = endPoint.x() - startPoint.x();
855 : // If beam is horizontal, one rectangle is enough
856 18197 : if (muse::RealIsEqual(startPoint.y(), endPoint.y())) {
857 10922 : RectF rect(startPoint.x(), startPoint.y(), beamHorizontalLength, _beamWidth / 2);
858 10922 : rect.adjust(0.0, -_beamWidth / 2, 0.0, 0.0);
859 10922 : shape.add(rect, parentElement);
860 10922 : return shape;
861 : }
862 : // If not, break the beam shape into multiple rectangles
863 7275 : double beamHeightDiff = endPoint.y() - startPoint.y();
864 7275 : int subBoxesCount = floor(beamHorizontalLength / parentElement->spatium());
865 7275 : subBoxesCount = std::max(subBoxesCount, 1); // at least one rectangle, of course (avoid division by zero)
866 7275 : double horizontalStep = beamHorizontalLength / subBoxesCount;
867 7275 : double verticalStep = beamHeightDiff / subBoxesCount;
868 7275 : std::vector<PointF> pointsOnBeamLine;
869 7275 : pointsOnBeamLine.reserve(subBoxesCount);
870 7275 : pointsOnBeamLine.push_back(startPoint);
871 52139 : for (int i = 0; i < subBoxesCount - 1; ++i) {
872 44864 : PointF nextPoint = pointsOnBeamLine.back() + PointF(horizontalStep, verticalStep);
873 44864 : pointsOnBeamLine.push_back(nextPoint);
874 : }
875 59414 : for (PointF point : pointsOnBeamLine) {
876 52139 : RectF rect(point.x(), point.y(), horizontalStep, _beamWidth / 2);
877 52139 : rect.adjust(0.0, -_beamWidth / 2, 0.0, 0.0);
878 52139 : shape.add(rect, parentElement);
879 : }
880 7275 : return shape;
881 7275 : }
|