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 "tremolotwochord.h"
24 :
25 : #include "draw/types/transform.h"
26 :
27 : #include "types/typesconv.h"
28 :
29 : #include "style/style.h"
30 :
31 : #include "rendering/score/beamtremololayout.h"
32 :
33 : #include "chord.h"
34 : #include "stem.h"
35 : #include "system.h"
36 : #include "stafftype.h"
37 :
38 : #include "log.h"
39 :
40 : using namespace mu;
41 : using namespace muse::draw;
42 : using namespace mu::engraving;
43 :
44 : namespace mu::engraving {
45 : //---------------------------------------------------------
46 : // tremoloStyle
47 : //---------------------------------------------------------
48 :
49 : static const ElementStyle TREMOLO_STYLE {
50 : { Sid::tremoloStyle, Pid::TREMOLO_STYLE }
51 : };
52 :
53 : //---------------------------------------------------------
54 : // Tremolo
55 : //---------------------------------------------------------
56 :
57 73 : TremoloTwoChord::TremoloTwoChord(DummyParentOr<Chord> parent)
58 73 : : BeamBase(ElementType::TREMOLO_TWOCHORD, parent, ElementFlag::MOVABLE)
59 : {
60 73 : initElementStyle(&TREMOLO_STYLE);
61 73 : }
62 :
63 8 : TremoloTwoChord::TremoloTwoChord(const TremoloTwoChord& t)
64 8 : : BeamBase(t)
65 : {
66 8 : setTremoloType(t.tremoloType());
67 8 : m_chord1 = t.chord1();
68 8 : m_chord2 = t.chord2();
69 8 : m_durationType = t.m_durationType;
70 8 : }
71 :
72 152 : TremoloTwoChord::~TremoloTwoChord()
73 : {
74 : //
75 : // delete all references from chords
76 : //
77 76 : if (m_chord1 && m_chord1->tremoloTwoChord() == this) {
78 0 : m_chord1->setTremoloTwoChord(nullptr);
79 : }
80 76 : if (m_chord2 && m_chord2->tremoloTwoChord() == this) {
81 0 : m_chord2->setTremoloTwoChord(nullptr);
82 : }
83 :
84 76 : clearBeamSegments();
85 152 : }
86 :
87 : //---------------------------------------------------------
88 : // chordMag
89 : //---------------------------------------------------------
90 :
91 0 : double TremoloTwoChord::chordMag() const
92 : {
93 0 : return chord() ? chord()->intrinsicMag() : 1.0;
94 : }
95 :
96 : //---------------------------------------------------------
97 : // minHeight
98 : //---------------------------------------------------------
99 :
100 0 : double TremoloTwoChord::minHeight() const
101 : {
102 0 : const double sw = style().styleS(Sid::tremoloLineWidth).val() * chordMag();
103 0 : const double td = style().styleS(Sid::tremoloDistance).val() * chordMag();
104 0 : return (lines() - 1) * td + sw;
105 : }
106 :
107 : //---------------------------------------------------------
108 : // chordBeamAnchor
109 : //---------------------------------------------------------
110 :
111 0 : PointF TremoloTwoChord::chordBeamAnchor(const ChordRest* chord, ChordBeamAnchorType anchorType) const
112 : {
113 0 : return rendering::score::BeamTremoloLayout::chordBeamAnchor(this->ldata(), chord, anchorType);
114 : }
115 :
116 : //---------------------------------------------------------
117 : // drag
118 : //---------------------------------------------------------
119 :
120 0 : RectF TremoloTwoChord::drag(EditData& ed)
121 : {
122 0 : int idx = directionIdx();
123 0 : double dy = ed.pos.y() - ed.lastPos.y();
124 :
125 0 : double y1 = m_beamFragment.py1[idx];
126 0 : double y2 = m_beamFragment.py2[idx];
127 :
128 0 : y1 += dy;
129 0 : y2 += dy;
130 :
131 0 : double _spatium = spatium();
132 : // Because of the logic in TremoloTwoChord::setProperty(),
133 : // changing Pid::BEAM_POS only has an effect if Pid::USER_MODIFIED is true.
134 0 : undoChangeProperty(Pid::USER_MODIFIED, true);
135 0 : undoChangeProperty(Pid::BEAM_POS, PairF(y1 / _spatium, y2 / _spatium));
136 0 : undoChangeProperty(Pid::GENERATED, false);
137 :
138 0 : triggerLayout();
139 :
140 0 : return canvasBoundingRect();
141 : }
142 :
143 : //---------------------------------------------------------
144 : // setTremoloType
145 : //---------------------------------------------------------
146 :
147 81 : void TremoloTwoChord::setTremoloType(TremoloType t)
148 : {
149 81 : IF_ASSERT_FAILED(isTremoloTwoChord(t)) {
150 0 : return;
151 : }
152 81 : m_tremoloType = t;
153 81 : switch (tremoloType()) {
154 26 : case TremoloType::R16:
155 : case TremoloType::C16:
156 26 : m_lines = 2;
157 26 : break;
158 33 : case TremoloType::R32:
159 : case TremoloType::C32:
160 33 : m_lines = 3;
161 33 : break;
162 8 : case TremoloType::R64:
163 : case TremoloType::C64:
164 8 : m_lines = 4;
165 8 : break;
166 0 : case TremoloType::R128:
167 : case TremoloType::C128:
168 0 : m_lines = 5;
169 0 : break;
170 0 : case TremoloType::R256:
171 : case TremoloType::C256:
172 0 : m_lines = 6;
173 0 : break;
174 14 : default:
175 14 : m_lines = 1;
176 14 : break;
177 : }
178 :
179 81 : styleChanged();
180 : }
181 :
182 : //---------------------------------------------------------
183 : // reset
184 : //---------------------------------------------------------
185 :
186 0 : void TremoloTwoChord::reset()
187 : {
188 0 : EngravingItem::reset();
189 0 : if (userModified()) {
190 : //undoChangeProperty(Pid::BEAM_POS, PropertyValue::fromValue(beamPos()));
191 0 : undoChangeProperty(Pid::USER_MODIFIED, false);
192 : }
193 0 : undoChangeProperty(Pid::STEM_DIRECTION, DirectionV::AUTO);
194 0 : resetProperty(Pid::BEAM_NO_SLOPE);
195 0 : }
196 :
197 : //---------------------------------------------------------
198 : // setBeamDirection
199 : //---------------------------------------------------------
200 :
201 1 : void TremoloTwoChord::setDirection(DirectionV d)
202 : {
203 1 : if (direction() == d) {
204 0 : return;
205 : }
206 :
207 1 : doSetDirection(d);
208 :
209 1 : if (d != DirectionV::AUTO) {
210 1 : setUp(d == DirectionV::UP);
211 : }
212 1 : if (m_chord1) {
213 1 : m_chord1->setStemDirection(d);
214 : }
215 1 : if (m_chord2) {
216 1 : m_chord2->setStemDirection(d);
217 : }
218 : }
219 :
220 : //---------------------------------------------------------
221 : // crossStaffBeamBetween
222 : // Return true if tremolo is two-note cross-staff and beams between staves
223 : //---------------------------------------------------------
224 :
225 0 : bool TremoloTwoChord::crossStaffBeamBetween() const
226 : {
227 0 : return ((m_chord1->staffMove() > m_chord2->staffMove()) && m_chord1->up() && !m_chord2->up())
228 0 : || ((m_chord1->staffMove() < m_chord2->staffMove()) && !m_chord1->up() && m_chord2->up());
229 : }
230 :
231 1050 : TDuration TremoloTwoChord::durationType() const
232 : {
233 1050 : return m_durationType;
234 : }
235 :
236 73 : void TremoloTwoChord::setDurationType(TDuration d)
237 : {
238 73 : if (m_durationType == d) {
239 0 : return;
240 : }
241 :
242 73 : m_durationType = d;
243 73 : styleChanged();
244 : }
245 :
246 : //---------------------------------------------------------
247 : // tremoloLen
248 : //---------------------------------------------------------
249 :
250 0 : Fraction TremoloTwoChord::tremoloLen() const
251 : {
252 0 : Fraction f;
253 0 : switch (lines()) {
254 0 : case 1: f.set(1, 8);
255 0 : break;
256 0 : case 2: f.set(1, 16);
257 0 : break;
258 0 : case 3: f.set(1, 32);
259 0 : break;
260 0 : case 4: f.set(1, 64);
261 0 : break;
262 0 : case 5: f.set(1, 128);
263 0 : break;
264 0 : case 6: f.set(1, 256);
265 0 : break;
266 : }
267 0 : return f;
268 : }
269 :
270 : //---------------------------------------------------------
271 : // setBeamPos
272 : //---------------------------------------------------------
273 :
274 0 : void TremoloTwoChord::setBeamPos(const PairF& bp)
275 : {
276 0 : int idx = directionIdx();
277 0 : setUserModified(true);
278 0 : setGenerated(false);
279 :
280 0 : double _spatium = spatium();
281 0 : m_beamFragment.py1[idx] = bp.first * _spatium;
282 0 : m_beamFragment.py2[idx] = bp.second * _spatium;
283 0 : }
284 :
285 : //---------------------------------------------------------
286 : // beamPos
287 : //---------------------------------------------------------
288 :
289 0 : PairF TremoloTwoChord::beamPos() const
290 : {
291 0 : int idx = directionIdx();
292 0 : double _spatium = spatium();
293 0 : return PairF(m_beamFragment.py1[idx] / _spatium, m_beamFragment.py2[idx] / _spatium);
294 : }
295 :
296 : //---------------------------------------------------------
297 : // triggerLayout
298 : //---------------------------------------------------------
299 :
300 235 : void TremoloTwoChord::triggerLayout() const
301 : {
302 235 : if (m_chord1 && m_chord2) {
303 34 : toChordRest(m_chord1)->triggerLayout();
304 34 : toChordRest(m_chord2)->triggerLayout();
305 : } else {
306 201 : EngravingItem::triggerLayout();
307 : }
308 235 : }
309 :
310 0 : bool TremoloTwoChord::needStartEditingAfterSelecting() const
311 : {
312 0 : return true;
313 : }
314 :
315 0 : int TremoloTwoChord::gripsCount() const
316 : {
317 0 : return 3;
318 : }
319 :
320 0 : Grip TremoloTwoChord::initialEditModeGrip() const
321 : {
322 0 : return Grip::END;
323 : }
324 :
325 0 : Grip TremoloTwoChord::defaultGrip() const
326 : {
327 0 : return Grip::MIDDLE;
328 : }
329 :
330 : //---------------------------------------------------------
331 : // gripsPositions
332 : //---------------------------------------------------------
333 :
334 0 : std::vector<PointF> TremoloTwoChord::gripsPositions(const EditData&) const
335 : {
336 0 : int idx = directionIdx();
337 :
338 0 : int y = pagePos().y();
339 0 : double beamStartX = m_startAnchor.x() + m_chord1->pageX();
340 0 : double beamEndX = m_endAnchor.x() + m_chord1->pageX(); // intentional--chord1 is start x
341 0 : double middleX = (beamStartX + beamEndX) / 2;
342 0 : double middleY = (m_beamFragment.py1[idx] + y + m_beamFragment.py2[idx] + y) / 2;
343 :
344 : return {
345 0 : PointF(beamStartX, m_beamFragment.py1[idx] + y),
346 0 : PointF(beamEndX, m_beamFragment.py2[idx] + y),
347 : PointF(middleX, middleY)
348 0 : };
349 : }
350 :
351 : //---------------------------------------------------------
352 : // dragGrip
353 : //---------------------------------------------------------
354 :
355 0 : void TremoloTwoChord::dragGrip(EditData& ed)
356 : {
357 0 : int idx = directionIdx();
358 0 : double dy = ed.delta.y();
359 0 : double y1 = m_beamFragment.py1[idx];
360 0 : double y2 = m_beamFragment.py2[idx];
361 :
362 0 : if (ed.curGrip == Grip::MIDDLE) {
363 0 : y1 += dy;
364 0 : y2 += dy;
365 0 : } else if (ed.curGrip == Grip::START) {
366 0 : y1 += dy;
367 0 : } else if (ed.curGrip == Grip::END) {
368 0 : y2 += dy;
369 : } else {
370 0 : UNREACHABLE;
371 : return;
372 : }
373 :
374 0 : double _spatium = spatium();
375 : // Because of the logic in Beam::setProperty(),
376 : // changing Pid::BEAM_POS only has an effect if Pid::USER_MODIFIED is true.
377 0 : undoChangeProperty(Pid::USER_MODIFIED, true);
378 0 : undoChangeProperty(Pid::BEAM_POS, PairF(y1 / _spatium, y2 / _spatium));
379 0 : undoChangeProperty(Pid::GENERATED, false);
380 :
381 0 : triggerLayout();
382 : }
383 :
384 : //---------------------------------------------------------
385 : // subtypeName
386 : //---------------------------------------------------------
387 :
388 0 : TranslatableString TremoloTwoChord::subtypeUserName() const
389 : {
390 0 : return TConv::userName(tremoloType());
391 : }
392 :
393 : //---------------------------------------------------------
394 : // accessibleInfo
395 : //---------------------------------------------------------
396 :
397 0 : String TremoloTwoChord::accessibleInfo() const
398 : {
399 0 : return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), translatedSubtypeUserName());
400 : }
401 :
402 : //---------------------------------------------------------
403 : // customStyleApplicable
404 : //---------------------------------------------------------
405 :
406 624 : bool TremoloTwoChord::customStyleApplicable() const
407 : {
408 1248 : return (durationType().type() == DurationType::V_HALF)
409 1248 : && (staffType()->group() != StaffGroup::TAB);
410 : }
411 :
412 : //---------------------------------------------------------
413 : // getProperty
414 : //---------------------------------------------------------
415 :
416 565 : PropertyValue TremoloTwoChord::getProperty(Pid propertyId) const
417 : {
418 565 : switch (propertyId) {
419 51 : case Pid::TREMOLO_TYPE:
420 51 : return int(m_tremoloType);
421 0 : case Pid::TREMOLO_STYLE:
422 0 : return int(m_style);
423 51 : case Pid::PLAY:
424 51 : return m_playTremolo;
425 463 : default:
426 463 : break;
427 : }
428 463 : return BeamBase::getProperty(propertyId);
429 : }
430 :
431 : //---------------------------------------------------------
432 : // setProperty
433 : //---------------------------------------------------------
434 :
435 231 : bool TremoloTwoChord::setProperty(Pid propertyId, const PropertyValue& val)
436 : {
437 231 : switch (propertyId) {
438 0 : case Pid::TREMOLO_TYPE:
439 0 : setTremoloType(TremoloType(val.toInt()));
440 0 : break;
441 229 : case Pid::TREMOLO_STYLE:
442 229 : if (customStyleApplicable()) {
443 31 : setTremoloStyle(TremoloStyle(val.toInt()));
444 : }
445 229 : break;
446 0 : case Pid::BEAM_POS:
447 0 : if (userModified()) {
448 0 : setBeamPos(val.value<PairF>());
449 : }
450 0 : break;
451 0 : case Pid::PLAY:
452 0 : setPlayTremolo(val.toBool());
453 0 : break;
454 2 : default:
455 2 : return BeamBase::setProperty(propertyId, val);
456 : }
457 229 : triggerLayout();
458 229 : return true;
459 : }
460 :
461 : //---------------------------------------------------------
462 : // propertyDefault
463 : //---------------------------------------------------------
464 :
465 561 : PropertyValue TremoloTwoChord::propertyDefault(Pid propertyId) const
466 : {
467 561 : switch (propertyId) {
468 0 : case Pid::TREMOLO_STYLE:
469 0 : return style().styleI(Sid::tremoloStyle);
470 51 : case Pid::PLAY:
471 51 : return true;
472 510 : default:
473 510 : return BeamBase::propertyDefault(propertyId);
474 : }
475 : }
476 :
477 : //---------------------------------------------------------
478 : // scanElements
479 : //---------------------------------------------------------
480 :
481 1 : void TremoloTwoChord::scanElements(std::function<void(EngravingItem*)> func)
482 : {
483 1 : if (chord() && chord()->tremoloChordType() == TremoloChordType::TremoloSecondChord) {
484 0 : return;
485 : }
486 1 : EngravingItem::scanElements(func);
487 : }
488 :
489 471 : void TremoloTwoChord::clearBeamSegments()
490 : {
491 471 : BeamSegment* chord1Segment = m_chord1 ? m_chord1->beamlet() : nullptr;
492 471 : BeamSegment* chord2Segment = m_chord2 ? m_chord2->beamlet() : nullptr;
493 :
494 471 : if (chord1Segment || chord2Segment) {
495 0 : for (BeamSegment* segment : m_beamSegments) {
496 0 : if (chord1Segment && chord1Segment == segment) {
497 0 : m_chord1->setBeamlet(nullptr);
498 0 : } else if (chord2Segment && chord2Segment == segment) {
499 0 : m_chord2->setBeamlet(nullptr);
500 : }
501 : }
502 : }
503 :
504 471 : BeamBase::clearBeamSegments();
505 471 : }
506 :
507 1100 : int TremoloTwoChord::maxCRMove() const
508 : {
509 1100 : return std::max(m_chord1->staffMove(), m_chord2->staffMove());
510 : }
511 :
512 825 : int TremoloTwoChord::minCRMove() const
513 : {
514 825 : return std::min(m_chord1->staffMove(), m_chord2->staffMove());
515 : }
516 :
517 : // used for palettes
518 0 : PainterPath TremoloTwoChord::basePath(double stretch) const
519 : {
520 0 : bool tradAlternate = m_style == TremoloStyle::TRADITIONAL_ALTERNATE;
521 0 : if (tradAlternate && muse::RealIsEqual(stretch, 0.)) {
522 : // this shape will have to be constructed after the stretch
523 : // is known
524 0 : return PainterPath();
525 : }
526 :
527 : // TODO: This should be a style setting, to replace tremoloStrokeLengthMultiplier
528 : static constexpr Spatium stemGapSp = 0.65_sp;
529 :
530 0 : const double sp = spatium() * chordMag();
531 :
532 : // overall width of two-note tremolos should not be changed if chordMag() isn't 1.0
533 0 : double w2 = sp * style().styleS(Sid::tremoloWidth).val() * .5 / chordMag();
534 0 : double lw = sp * style().styleS(Sid::tremoloLineWidth).val();
535 0 : double td = sp * style().styleS(Sid::tremoloDistance).val();
536 :
537 0 : PainterPath ppath;
538 :
539 : // first line
540 0 : ppath.addRect(-w2, 0.0, 2.0 * w2, lw);
541 0 : double ty = td;
542 :
543 : // other lines
544 0 : for (int i = 1; i < m_lines; i++) {
545 0 : if (tradAlternate) {
546 0 : double stemWidth1 = m_chord1->stem()->lineWidthMag() / stretch;
547 0 : double stemWidth2 = m_chord2->stem()->lineWidthMag() / stretch;
548 0 : double inset = stemGapSp.toAbsolute(spatium()) / stretch;
549 :
550 0 : ppath.addRect(-w2 + inset + stemWidth1, ty,
551 0 : 2.0 * w2 - (inset * 2.) - (stemWidth2 + stemWidth1), lw);
552 : } else {
553 0 : ppath.addRect(-w2, ty, 2.0 * w2, lw);
554 : }
555 0 : ty += td;
556 : }
557 :
558 0 : if (!chord()) {
559 : // for the palette or for one-note tremolos
560 0 : Transform shearTransform;
561 0 : shearTransform.shear(0.0, -(lw / 2.0) / w2);
562 0 : ppath = shearTransform.map(ppath);
563 : }
564 :
565 0 : return ppath;
566 0 : }
567 :
568 0 : void TremoloTwoChord::computeShape()
569 : {
570 0 : m_path = basePath();
571 0 : setbbox(m_path.boundingRect());
572 0 : }
573 : }
|