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 "gradualtempochange.h"
24 :
25 : #include "measure.h"
26 : #include "rehearsalmark.h"
27 : #include "score.h"
28 : #include "segment.h"
29 : #include "staff.h"
30 : #include "system.h"
31 : #include "tempotext.h"
32 : #include "text.h"
33 :
34 : #include "types/typesconv.h"
35 :
36 : #include "log.h"
37 :
38 : using namespace mu;
39 : using namespace mu::engraving;
40 :
41 : static const ElementStyle tempoStyle {
42 : { Sid::tempoChangeSystemFlag, Pid::SYSTEM_FLAG },
43 : { Sid::tempoChangePlacement, Pid::PLACEMENT },
44 : { Sid::tempoChangeMinDistance, Pid::MIN_DISTANCE },
45 :
46 : { Sid::tempoChangeColor, Pid::COLOR },
47 :
48 : { Sid::tempoChangeFontFace, Pid::BEGIN_FONT_FACE },
49 : { Sid::tempoChangeFontFace, Pid::CONTINUE_FONT_FACE },
50 : { Sid::tempoChangeFontFace, Pid::END_FONT_FACE },
51 :
52 : { Sid::tempoChangeFontSize, Pid::BEGIN_FONT_SIZE },
53 : { Sid::tempoChangeFontSize, Pid::CONTINUE_FONT_SIZE },
54 : { Sid::tempoChangeFontSize, Pid::END_FONT_SIZE },
55 :
56 : { Sid::tempoChangeFontStyle, Pid::BEGIN_FONT_STYLE },
57 : { Sid::tempoChangeFontStyle, Pid::CONTINUE_FONT_STYLE },
58 : { Sid::tempoChangeFontStyle, Pid::END_FONT_STYLE },
59 :
60 : { Sid::tempoChangeAlign, Pid::BEGIN_TEXT_ALIGN },
61 : { Sid::tempoChangeAlign, Pid::CONTINUE_TEXT_ALIGN },
62 : { Sid::tempoChangeAlign, Pid::END_TEXT_ALIGN },
63 :
64 : { Sid::tempoChangePosition, Pid::BEGIN_TEXT_POSITION },
65 : { Sid::tempoChangePosition, Pid::CONTINUE_TEXT_POSITION },
66 : { Sid::tempoChangePosition, Pid::END_TEXT_POSITION },
67 :
68 : { Sid::tempoChangeFontSpatiumDependent, Pid::SIZE_SPATIUM_DEPENDENT },
69 : { Sid::tempoChangeLineWidth, Pid::LINE_WIDTH },
70 : { Sid::tempoChangeLineStyle, Pid::LINE_STYLE },
71 : { Sid::tempoChangeDashLineLen, Pid::DASH_LINE_LEN },
72 : { Sid::tempoChangeDashGapLen, Pid::DASH_GAP_LEN },
73 : { Sid::tempoChangeFontSpatiumDependent, Pid::TEXT_SIZE_SPATIUM_DEPENDENT },
74 :
75 : { Sid::gradualTempoChangeEndLineArrowHeight, Pid::END_LINE_ARROW_HEIGHT },
76 : { Sid::gradualTempoChangeEndLineArrowWidth, Pid::END_LINE_ARROW_WIDTH },
77 : { Sid::gradualTempoChangeBeginLineArrowHeight, Pid::BEGIN_LINE_ARROW_HEIGHT },
78 : { Sid::gradualTempoChangeBeginLineArrowWidth, Pid::BEGIN_LINE_ARROW_WIDTH },
79 : { Sid::gradualTempoChangeEndFilledArrowHeight, Pid::END_FILLED_ARROW_HEIGHT },
80 : { Sid::gradualTempoChangeEndFilledArrowWidth, Pid::END_FILLED_ARROW_WIDTH },
81 : { Sid::gradualTempoChangeBeginFilledArrowHeight, Pid::BEGIN_FILLED_ARROW_HEIGHT },
82 : { Sid::gradualTempoChangeBeginFilledArrowWidth, Pid::BEGIN_FILLED_ARROW_WIDTH },
83 :
84 : { Sid::tempoChangeMusicalSymbolSize, Pid::BEGIN_TEXT_MUSIC_SYMBOLS_SIZE },
85 : { Sid::tempoChangeMusicalSymbolSize, Pid::CONTINUE_TEXT_MUSIC_SYMBOLS_SIZE },
86 : { Sid::tempoChangeMusicalSymbolSize, Pid::END_TEXT_MUSIC_SYMBOLS_SIZE },
87 : { Sid::dummyMusicalSymbolsScale, Pid::BEGIN_TEXT_MUSICAL_SYMBOLS_SCALE },
88 : { Sid::dummyMusicalSymbolsScale, Pid::CONTINUE_TEXT_MUSICAL_SYMBOLS_SCALE },
89 : { Sid::dummyMusicalSymbolsScale, Pid::END_TEXT_MUSICAL_SYMBOLS_SCALE },
90 : };
91 :
92 : static const ElementStyle tempoSegmentStyle {
93 : { Sid::tempoChangeMinDistance, Pid::MIN_DISTANCE }
94 : };
95 :
96 : static const std::unordered_map<GradualTempoChangeType, double> DEFAULT_FACTORS_MAP {
97 : { GradualTempoChangeType::Accelerando, 1.33 },
98 : { GradualTempoChangeType::Allargando, 0.75 },
99 : { GradualTempoChangeType::Calando, 0.5 },
100 : { GradualTempoChangeType::Lentando, 0.75 },
101 : { GradualTempoChangeType::Morendo, 0.5 },
102 : { GradualTempoChangeType::Precipitando, 1.15 },
103 : { GradualTempoChangeType::Rallentando, 0.75 },
104 : { GradualTempoChangeType::Ritardando, 0.75 },
105 : { GradualTempoChangeType::Smorzando, 0.5 },
106 : { GradualTempoChangeType::Sostenuto, 0.95 },
107 : { GradualTempoChangeType::Stringendo, 1.5 }
108 : };
109 :
110 91 : GradualTempoChange::GradualTempoChange(DummyParentOr<EngravingItem> parent)
111 91 : : TextLineBase(ElementType::GRADUAL_TEMPO_CHANGE, parent, ElementFlag::SYSTEM)
112 : {
113 91 : initElementStyle(&tempoStyle);
114 :
115 91 : resetProperty(Pid::LINE_VISIBLE);
116 91 : resetProperty(Pid::BEGIN_TEXT_PLACE);
117 91 : resetProperty(Pid::BEGIN_TEXT);
118 91 : resetProperty(Pid::CONTINUE_TEXT_PLACE);
119 91 : resetProperty(Pid::CONTINUE_TEXT);
120 91 : resetProperty(Pid::END_TEXT_PLACE);
121 91 : resetProperty(Pid::END_TEXT);
122 91 : }
123 :
124 3 : GradualTempoChange* GradualTempoChange::clone() const
125 : {
126 3 : return new GradualTempoChange(*this);
127 : }
128 :
129 12 : LineSegment* GradualTempoChange::createLineSegment()
130 : {
131 12 : GradualTempoChangeSegment* lineSegment = new GradualTempoChangeSegment(this);
132 12 : lineSegment->setTrack(track());
133 12 : return lineSegment;
134 : }
135 :
136 0 : GradualTempoChangeType GradualTempoChange::tempoChangeType() const
137 : {
138 0 : return m_tempoChangeType;
139 : }
140 :
141 15 : ChangeMethod GradualTempoChange::easingMethod() const
142 : {
143 15 : return m_tempoEasingMethod;
144 : }
145 :
146 5 : void GradualTempoChange::setTempoChangeType(const GradualTempoChangeType type)
147 : {
148 5 : m_tempoChangeType = type;
149 5 : }
150 :
151 65 : double GradualTempoChange::tempoChangeFactor() const
152 : {
153 65 : if (m_tempoChangeFactor.has_value()) {
154 15 : return m_tempoChangeFactor.value();
155 : }
156 :
157 50 : return muse::value(DEFAULT_FACTORS_MAP, m_tempoChangeType, 1.0);
158 : }
159 :
160 1613 : PropertyValue GradualTempoChange::getProperty(Pid id) const
161 : {
162 1613 : switch (id) {
163 50 : case Pid::TEMPO_CHANGE_TYPE:
164 50 : return m_tempoChangeType;
165 50 : case Pid::TEMPO_EASING_METHOD:
166 50 : return m_tempoEasingMethod;
167 50 : case Pid::TEMPO_CHANGE_FACTOR:
168 50 : return tempoChangeFactor();
169 50 : case Pid::SNAP_AFTER:
170 50 : return snapToItemAfter();
171 0 : case Pid::TEMPO_ALIGN_RIGHT_OF_REHEARSAL_MARK:
172 0 : return m_alignRightOfRehearsalMark;
173 1413 : default:
174 1413 : return TextLineBase::getProperty(id);
175 : }
176 : }
177 :
178 4663 : bool GradualTempoChange::setProperty(Pid id, const PropertyValue& val)
179 : {
180 4663 : switch (id) {
181 6 : case Pid::TEMPO_CHANGE_TYPE:
182 6 : m_tempoChangeType = val.value<GradualTempoChangeType>();
183 6 : break;
184 0 : case Pid::TEMPO_EASING_METHOD:
185 0 : m_tempoEasingMethod = val.value<ChangeMethod>();
186 0 : break;
187 4 : case Pid::TEMPO_CHANGE_FACTOR:
188 4 : m_tempoChangeFactor = val.toReal();
189 4 : break;
190 0 : case Pid::SNAP_AFTER:
191 0 : setSnapToItemAfter(val.toBool());
192 0 : break;
193 0 : case Pid::TEMPO_ALIGN_RIGHT_OF_REHEARSAL_MARK:
194 0 : m_alignRightOfRehearsalMark = val.toBool();
195 0 : break;
196 4653 : default:
197 4653 : if (!TextLineBase::setProperty(id, val)) {
198 0 : return false;
199 : }
200 4653 : break;
201 : }
202 :
203 4663 : triggerLayout();
204 4663 : return true;
205 : }
206 :
207 2112 : PropertyValue GradualTempoChange::propertyDefault(Pid propertyId) const
208 : {
209 2112 : switch (propertyId) {
210 0 : case Pid::ALIGN:
211 0 : return style().styleV(Sid::tempoChangeAlign);
212 :
213 2 : case Pid::LINE_WIDTH:
214 2 : return style().styleV(Sid::tempoChangeLineWidth);
215 2 : case Pid::LINE_STYLE:
216 2 : return style().styleV(Sid::tempoChangeLineStyle);
217 141 : case Pid::LINE_VISIBLE:
218 141 : return true;
219 :
220 100 : case Pid::CONTINUE_TEXT_OFFSET:
221 : case Pid::END_TEXT_OFFSET:
222 100 : return PropertyValue::fromValue(PointF(0, 0));
223 :
224 2 : case Pid::BEGIN_FONT_STYLE:
225 2 : return style().styleV(Sid::tempoChangeFontStyle);
226 :
227 423 : case Pid::BEGIN_TEXT:
228 : case Pid::CONTINUE_TEXT:
229 : case Pid::END_TEXT:
230 423 : return "";
231 :
232 50 : case Pid::BEGIN_HOOK_TYPE:
233 50 : return HookType::NONE;
234 :
235 423 : case Pid::BEGIN_TEXT_PLACE:
236 : case Pid::CONTINUE_TEXT_PLACE:
237 : case Pid::END_TEXT_PLACE:
238 423 : return TextPlace::AUTO;
239 :
240 50 : case Pid::TEMPO_CHANGE_TYPE:
241 50 : return GradualTempoChangeType::Undefined;
242 50 : case Pid::TEMPO_EASING_METHOD:
243 50 : return ChangeMethod::NORMAL;
244 50 : case Pid::TEMPO_CHANGE_FACTOR:
245 100 : return muse::value(DEFAULT_FACTORS_MAP, m_tempoChangeType, 1.0);
246 :
247 50 : case Pid::SNAP_AFTER:
248 50 : return true;
249 :
250 0 : case Pid::TEMPO_ALIGN_RIGHT_OF_REHEARSAL_MARK:
251 0 : return true;
252 :
253 24 : case Pid::TEXT_STYLE:
254 24 : return TextStyleType::TEMPO_CHANGE;
255 :
256 745 : default:
257 745 : return TextLineBase::propertyDefault(propertyId);
258 : }
259 : }
260 :
261 3993 : Sid GradualTempoChange::getPropertyStyle(Pid id) const
262 : {
263 3993 : switch (id) {
264 103 : case Pid::PLACEMENT:
265 103 : return Sid::tempoChangePlacement;
266 291 : case Pid::BEGIN_FONT_FACE:
267 : case Pid::CONTINUE_FONT_FACE:
268 : case Pid::END_FONT_FACE:
269 291 : return Sid::tempoChangeFontFace;
270 291 : case Pid::BEGIN_FONT_SIZE:
271 : case Pid::CONTINUE_FONT_SIZE:
272 : case Pid::END_FONT_SIZE:
273 291 : return Sid::tempoChangeFontSize;
274 289 : case Pid::BEGIN_FONT_STYLE:
275 : case Pid::CONTINUE_FONT_STYLE:
276 : case Pid::END_FONT_STYLE:
277 289 : return Sid::tempoChangeFontStyle;
278 291 : case Pid::BEGIN_TEXT_ALIGN:
279 : case Pid::CONTINUE_TEXT_ALIGN:
280 : case Pid::END_TEXT_ALIGN:
281 291 : return Sid::tempoChangeAlign;
282 0 : case Pid::BEGIN_TEXT:
283 0 : return Sid::letRingText;
284 2728 : default:
285 2728 : break;
286 : }
287 2728 : return TextLineBase::getPropertyStyle(id);
288 : }
289 :
290 48 : bool GradualTempoChange::adjustForRehearsalMark(bool start) const
291 : {
292 48 : const Segment* segment = start ? startSegment() : endSegment();
293 48 : if (!m_alignRightOfRehearsalMark || !segment) {
294 0 : return false;
295 : }
296 :
297 48 : const RehearsalMark* rehearsalMark = toRehearsalMark(segment->findAnnotation(ElementType::REHEARSAL_MARK, track(), track()));
298 48 : if (!rehearsalMark) {
299 44 : return false;
300 : }
301 :
302 4 : double staffHeight = staff() && placeBelow() ? staff()->staffHeight(tick()) : 0.0;
303 4 : double tempoChangePos = staffHeight + defaultPos().y() + (autoplace() ? absoluteFromSpatium(minDistance()) : 0.0);
304 4 : RectF rehearsalMarkBbox = rehearsalMark ? rehearsalMark->ldata()->bbox().translated(rehearsalMark->pos()) : RectF();
305 :
306 4 : const bool sameSide = placeAbove() == rehearsalMark->placeAbove();
307 4 : const bool collision = muse::RealIsEqualOrMore(rehearsalMarkBbox.bottom(), tempoChangePos) && muse::RealIsEqualOrLess(
308 4 : rehearsalMarkBbox.top(), tempoChangePos);
309 :
310 4 : return sameSide && collision;
311 : }
312 :
313 44 : PointF GradualTempoChange::linePos(Grip grip, System** system) const
314 : {
315 44 : bool start = grip == Grip::START;
316 44 : PointF defaultPos = TextLineBase::linePos(grip, system);
317 44 : if (!adjustForRehearsalMark(start)) {
318 40 : return defaultPos;
319 : }
320 :
321 4 : const Segment* segment = start ? startSegment() : endSegment();
322 4 : const RehearsalMark* rehearsalMark = toRehearsalMark(segment->findAnnotation(ElementType::REHEARSAL_MARK, track(), track()));
323 4 : RectF rehearsalMarkBbox = rehearsalMark ? rehearsalMark->ldata()->bbox().translated(rehearsalMark->pos()) : RectF();
324 :
325 4 : PointF rehearsalMarkPos = segment->pos() + segment->measure()->pos();
326 4 : rehearsalMarkBbox.translate(rehearsalMarkPos);
327 :
328 4 : Text* text = start ? toGradualTempoChangeSegment(frontSegment())->text() : toGradualTempoChangeSegment(backSegment())->endText();
329 :
330 4 : const double sp = spatium();
331 :
332 4 : double padding = sp;
333 4 : if (text) {
334 4 : const double fontSizeScaleFactor = text->size() / 10.0;
335 4 : padding = 0.5 * sp * fontSizeScaleFactor;
336 : }
337 :
338 4 : padding *= start ? 1.0 : -1.0;
339 4 : double x = (start ? rehearsalMarkBbox.right() : rehearsalMarkBbox.left()) + padding;
340 :
341 4 : *system = segment->measure()->system();
342 :
343 4 : x = start ? std::max(x, defaultPos.x()) : x;
344 :
345 4 : return PointF(x, 0.0);
346 : }
347 :
348 1 : TranslatableString GradualTempoChange::subtypeUserName() const
349 : {
350 1 : return TConv::userName(m_tempoChangeType);
351 : }
352 :
353 29 : Sid GradualTempoChange::defaultPosSid() const
354 : {
355 29 : return placeAbove() ? Sid::tempoChangePosAbove : Sid::tempoChangePosBelow;
356 : }
357 :
358 12 : GradualTempoChangeSegment::GradualTempoChangeSegment(GradualTempoChange* annotation)
359 0 : : TextLineBaseSegment(ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT, annotation,
360 12 : ElementFlag::MOVABLE | ElementFlag::ON_STAFF | ElementFlag::SYSTEM)
361 : {
362 12 : initElementStyle(&tempoSegmentStyle);
363 12 : m_text->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
364 12 : m_endText->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
365 12 : }
366 :
367 2 : GradualTempoChangeSegment* GradualTempoChangeSegment::clone() const
368 : {
369 2 : return new GradualTempoChangeSegment(*this);
370 : }
371 :
372 70 : GradualTempoChange* GradualTempoChangeSegment::tempoChange() const
373 : {
374 70 : return static_cast<GradualTempoChange*>(spanner());
375 : }
376 :
377 22 : GradualTempoChangeSegment* GradualTempoChangeSegment::findElementToSnapBefore() const
378 : {
379 22 : const System* sys = system();
380 22 : IF_ASSERT_FAILED(sys) {
381 0 : return nullptr;
382 : }
383 :
384 22 : GradualTempoChange* thisTempoChange = tempoChange();
385 22 : Fraction startTick = thisTempoChange->tick();
386 22 : if (!sys->measures().empty() && startTick == sys->measures().front()->tick()) {
387 6 : return nullptr;
388 : }
389 :
390 16 : auto intervals = score()->spannerMap().findOverlapping(startTick.ticks(), startTick.ticks());
391 44 : for (auto interval : intervals) {
392 28 : Spanner* spanner = interval.value;
393 48 : bool isValidTempoChange = spanner->isGradualTempoChange() && !spanner->segmentsEmpty() && spanner->visible()
394 48 : && spanner != thisTempoChange;
395 28 : if (!isValidTempoChange) {
396 25 : continue;
397 : }
398 :
399 3 : GradualTempoChange* precedingTempoChange = toGradualTempoChange(spanner);
400 3 : bool endsMatch = precedingTempoChange->track() == thisTempoChange->track()
401 0 : && precedingTempoChange->tick2() == startTick
402 3 : && precedingTempoChange->placeAbove() == thisTempoChange->placeAbove();
403 :
404 3 : if (endsMatch && precedingTempoChange->snapToItemAfter()) {
405 0 : return toGradualTempoChangeSegment(precedingTempoChange->backSegment());
406 : }
407 : }
408 :
409 16 : return nullptr;
410 16 : }
411 :
412 22 : TempoText* GradualTempoChangeSegment::findElementToSnapAfter() const
413 : {
414 22 : if (!tempoChange()->snapToItemAfter()) {
415 0 : return nullptr;
416 : }
417 :
418 22 : System* sys = system();
419 22 : IF_ASSERT_FAILED(sys) {
420 0 : return nullptr;
421 : }
422 :
423 : // Note: we don't need to look for a tempoChange after.
424 : // It is the next tempoChange which looks for a tempoChange before.
425 22 : Fraction refTick = tempoChange()->tick2();
426 22 : Measure* measure = score()->tick2measureMM(refTick);
427 22 : if (!measure) {
428 0 : return nullptr;
429 : }
430 :
431 155 : for (Segment* segment = measure->last(); segment; segment = segment->prev1()) {
432 155 : if (segment->system() != sys) {
433 105 : continue;
434 : }
435 151 : Fraction segmentTick = segment->tick();
436 151 : if (segmentTick > refTick) {
437 101 : continue;
438 : }
439 50 : if (segmentTick < refTick) {
440 18 : break;
441 : }
442 32 : EngravingItem* tempoText = segment->findAnnotation(ElementType::TEMPO_TEXT, track(), track());
443 32 : if (tempoText && tempoText->placeAbove() == placeAbove() && tempoText->visible()) {
444 4 : return toTempoText(tempoText);
445 : }
446 : }
447 :
448 18 : return nullptr;
449 : }
450 :
451 0 : void GradualTempoChangeSegment::endEdit(EditData& editData)
452 : {
453 0 : TextLineBaseSegment::endEdit(editData);
454 0 : }
|