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) 2023 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 : #include "autoplace.h"
23 :
24 : #include "dom/harmony.h"
25 : #include "style/style.h"
26 :
27 : #include "dom/chordrest.h"
28 : #include "dom/lyrics.h"
29 : #include "dom/score.h"
30 : #include "dom/segment.h"
31 : #include "dom/spanner.h"
32 : #include "dom/staff.h"
33 : #include "dom/system.h"
34 : #include "dom/measure.h"
35 :
36 : #include "log.h"
37 :
38 : using namespace muse;
39 : using namespace mu::engraving;
40 : using namespace mu::engraving::rendering::score;
41 :
42 9882 : void Autoplace::autoplaceSegmentElement(const EngravingItem* item, EngravingItem::LayoutData* ldata, bool above, bool add)
43 : {
44 9882 : const Segment* segment = toSegment(item->findAncestor(ElementType::SEGMENT));
45 9882 : const Measure* measure = segment ? segment->measure() : nullptr;
46 9882 : const System* system = measure ? measure->system() : nullptr;
47 9882 : IF_ASSERT_FAILED(system) {
48 0 : return;
49 : }
50 9882 : LD_CONDITION(segment->ldata()->isSetPos());
51 9882 : LD_CONDITION(measure->ldata()->isSetPos());
52 :
53 9882 : const double spatium = item->style().spatium() * item->staff()->staffMag(item);
54 : // TODO: proper item-to-item table for horizontal clearance in skyline
55 9882 : const double minSkylineHorizontalClearance = item->isArticulationOrFermata() ? 0.0 : item->style().styleAbsolute(
56 7149 : Sid::skylineMinHorizontalClearance) * item->mag();
57 :
58 9882 : doAutoplaceElement(item, ldata, system, above, add, minSkylineHorizontalClearance, spatium);
59 : }
60 :
61 6319 : void Autoplace::autoplaceMeasureElement(const EngravingItem* item, EngravingItem::LayoutData* ldata, bool above, bool add)
62 : {
63 6319 : const Measure* measure = toMeasure(item->ownershipParent());
64 6319 : const System* system = measure ? measure->system() : nullptr;
65 6319 : IF_ASSERT_FAILED(system) {
66 0 : return;
67 : }
68 6319 : LD_CONDITION(measure->ldata()->isSetPos());
69 :
70 6319 : const double spatium = item->style().spatium() * item->staff()->staffMag(item);
71 : // TODO: check this value for measure-owned items
72 : // const double minSkylineHorizontalClearance = item->isMeasureNumber() ? 0.0 : item->style().styleAbsolute(
73 : // Sid::skylineMinHorizontalClearance) * item->mag();
74 6319 : const double minSkylineHorizontalClearance = 0.0;
75 6319 : doAutoplaceElement(item, ldata, system, above, add, minSkylineHorizontalClearance, spatium);
76 : }
77 :
78 1303 : void Autoplace::autoplaceSpannerSegment(const SpannerSegment* item, EngravingItem::LayoutData* ldata)
79 : {
80 1303 : if (item->spanner()->anchor() == Spanner::Anchor::NOTE) {
81 0 : return;
82 : }
83 1303 : const System* system = item->system();
84 1303 : IF_ASSERT_FAILED(system) {
85 0 : return;
86 : }
87 1303 : const double mag = !item->systemFlag() && !item->spanner()->systemFlag() ? item->staff()->staffMag(item->spanner()->tick()) : 1.0;
88 1303 : const double spatium = item->style().spatium() * mag;
89 :
90 1303 : const bool above = item->spanner()->placeAbove();
91 1303 : doAutoplaceElement(item, ldata, system, above, /* add = */ false, /* minSkylineHorizontalClearance = */ 0.0, spatium);
92 : }
93 :
94 17498 : double Autoplace::distanceToMoveItem(const EngravingItem* item, const SkylineLine& staffSkyline, const Shape& shape, bool above,
95 : double minSkylineHorizontalClearance, double minDistance)
96 : {
97 34996 : const SkylineLine filteredSkyline = staffSkyline.getFilteredCopy([item](const ShapeElement& shapeEl) {
98 426237 : const EngravingItem* skylineItem = shapeEl.item();
99 426237 : if (!skylineItem) {
100 6095 : return false;
101 : }
102 420142 : return itemsShouldIgnoreEachOther(item, skylineItem);
103 17498 : });
104 :
105 17498 : if (filteredSkyline.elements().empty()) {
106 1315 : return 0.0;
107 : }
108 :
109 16183 : const double d = above ? filteredSkyline.minDistanceToShapeAbove(shape, minSkylineHorizontalClearance)
110 3105 : : filteredSkyline.minDistanceToShapeBelow(shape, minSkylineHorizontalClearance);
111 :
112 16183 : if (!(d > -minDistance)) {
113 9918 : return 0.0;
114 : }
115 :
116 6265 : const double yd = (d + minDistance) * (above ? -1.0 : 1.0);
117 :
118 6265 : return yd;
119 17498 : }
120 :
121 17504 : void Autoplace::doAutoplaceElement(const EngravingItem* item, EngravingItem::LayoutData* ldata, const System* system, bool above, bool add,
122 : double minSkylineHorizontalClearance, double spatium)
123 : {
124 17504 : DO_ASSERT(autoplaceAppliesToType(item->type()));
125 17504 : IF_ASSERT_FAILED(system) {
126 6 : return;
127 : }
128 :
129 17504 : if (!item->autoplace() || !item->ownershipParent()) {
130 6 : return;
131 : }
132 :
133 17498 : const staff_idx_t staffIdx = item->effectiveStaffIdx();
134 17498 : if (staffIdx == muse::nidx) {
135 0 : return;
136 : }
137 :
138 17498 : LD_CONDITION(ldata->isSetPos());
139 17498 : LD_CONDITION(ldata->isSetBbox());
140 :
141 17498 : const double minDistance = item->minDistance().toAbsolute(spatium);
142 :
143 17498 : SysStaff* sysStaff = system->staff(staffIdx);
144 17498 : SkylineLine& staffSkyline = above ? sysStaff->skyline().north() : sysStaff->skyline().south();
145 : // TODO [J.M] staffOffset will be applied twice for spanner segments.
146 : // We really need to sort out staffOffset and have it applied as early and infrequently as possible. Can't keep up with ~30 callsites
147 : // Shape shape = item->ldata()->shape().translate(item->systemPos() - item->offset() + item->staffOffset());
148 17498 : Shape shape = item->ldata()->shape();
149 17498 : if (item->isSpannerSegment()) {
150 1303 : shape.translate(item->ldata()->pos());
151 : } else {
152 16195 : shape.translate(item->systemPos() - item->offset() + item->staffOffset());
153 : }
154 :
155 17498 : const double yd = distanceToMoveItem(item, staffSkyline, shape, above, minSkylineHorizontalClearance, minDistance);
156 :
157 17498 : ldata->moveY(yd);
158 17498 : shape.translateY(yd);
159 :
160 17498 : if (add && item->addToSkyline()) {
161 16027 : staffSkyline.add(shape.translated(item->offset()));
162 : }
163 17498 : }
164 :
165 450194 : bool Autoplace::itemsShouldIgnoreEachOther(const EngravingItem* itemToAutoplace, const EngravingItem* itemInSkyline)
166 : {
167 450194 : if (itemToAutoplace == itemInSkyline) {
168 29 : return true;
169 : }
170 :
171 450165 : const EngravingItem* skylineItemOwner = itemInSkyline->ownershipParentItem();
172 450165 : if (itemInSkyline->isText() && skylineItemOwner && skylineItemOwner->isSLineSegment()) {
173 0 : return itemsShouldIgnoreEachOther(itemToAutoplace, skylineItemOwner);
174 : }
175 :
176 450165 : ElementType type1 = itemToAutoplace->type();
177 450165 : ElementType type2 = itemInSkyline->type();
178 :
179 450165 : if (type1 == ElementType::TIMESIG) {
180 0 : return type2 != ElementType::KEYSIG;
181 : }
182 :
183 450165 : if (type1 == ElementType::FRET_DIAGRAM && (type2 == ElementType::FRET_DIAGRAM || type2 == ElementType::HARMONY)) {
184 766 : bool isFretDiagAgainstItsOwnHarmony = skylineItemOwner == itemToAutoplace;
185 766 : bool areOnDifferentSegments = itemToAutoplace->findAncestor(ElementType::SEGMENT)
186 766 : != itemInSkyline->findAncestor(ElementType::SEGMENT);
187 766 : return isFretDiagAgainstItsOwnHarmony || areOnDifferentSegments;
188 : }
189 :
190 449399 : if ((type1 == ElementType::DYNAMIC || type1 == ElementType::HAIRPIN_SEGMENT)
191 75524 : && (type2 == ElementType::DYNAMIC || type2 == ElementType::HAIRPIN_SEGMENT)) {
192 3921 : return true;
193 : }
194 :
195 445478 : if (itemToAutoplace->isLyricsLineSegment() && itemInSkyline->isLyricsLineSegment()) {
196 0 : const LyricsLineSegment* a = toLyricsLineSegment(itemToAutoplace);
197 0 : const LyricsLineSegment* b = toLyricsLineSegment(itemInSkyline);
198 0 : return a->verse() == b->verse() && a->lyricsPlaceAbove() == b->lyricsPlaceAbove();
199 : }
200 :
201 445478 : if (type1 == type2) {
202 : // Items of same type should ignore each other in most cases
203 : static const std::set<ElementType> TEXT_BASED_TYPES_WHICH_IGNORE_EACH_OTHER {
204 : ElementType::DYNAMIC,
205 : ElementType::EXPRESSION,
206 : ElementType::STICKING
207 45131 : };
208 45105 : return !itemToAutoplace->isTextBase() || muse::contains(TEXT_BASED_TYPES_WHICH_IGNORE_EACH_OTHER, type1);
209 : }
210 :
211 400373 : if ((type1 == ElementType::DYNAMIC || type1 == ElementType::EXPRESSION)
212 41873 : && (type2 == ElementType::DYNAMIC || type2 == ElementType::EXPRESSION)) {
213 : // Dynamics and expressions should ignore each other if on the same segment
214 68 : return itemToAutoplace->parent() == itemInSkyline->parent();
215 : }
216 :
217 400305 : if ((type1 == ElementType::TUPLET || type1 == ElementType::STAFF_LINES)
218 97641 : && (type2 == ElementType::STAFF_LINES || type2 == ElementType::TUPLET)) {
219 6692 : const Score* score = itemToAutoplace->score();
220 6692 : const bool outOfStaff = score ? !score->style().styleB(Sid::tupletOutOfStaff) : false;
221 6692 : return outOfStaff;
222 : }
223 :
224 393613 : if ((type1 == ElementType::FIGURED_BASS || type1 == ElementType::FIGURED_BASS_ITEM)
225 2307 : && (type2 == ElementType::FIGURED_BASS || type2 == ElementType::FIGURED_BASS_ITEM)) {
226 651 : return true;
227 : }
228 :
229 392962 : if (itemToAutoplace->isArticulationOrFermata() && itemInSkyline->isArticulationOrFermata()) {
230 : // Ignore fermatas and articulations on other segments
231 1715 : return itemToAutoplace->findAncestor(ElementType::SEGMENT) != itemInSkyline->findAncestor(ElementType::SEGMENT);
232 : }
233 :
234 391247 : return itemToAutoplace->ldata()->itemSnappedBefore() == itemInSkyline || itemToAutoplace->ldata()->itemSnappedAfter() == itemInSkyline;
235 : }
|