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 "hammeronpulloff.h"
24 :
25 : #include "iengravingconfiguration.h" // IWYU pragma: keep
26 :
27 : #include "mscore.h"
28 : #include "note.h"
29 : #include "score.h"
30 : #include "stafftype.h"
31 : #include "style/textstyle.h"
32 : #include "system.h"
33 :
34 : namespace mu::engraving {
35 52 : HammerOnPullOff::HammerOnPullOff(DummyParentOr<EngravingItem> parent)
36 52 : : Slur(parent, ElementType::HAMMER_ON_PULL_OFF)
37 : {
38 52 : }
39 :
40 5 : HammerOnPullOff::HammerOnPullOff(const HammerOnPullOff& other)
41 5 : : Slur(other)
42 : {
43 5 : }
44 :
45 57 : HammerOnPullOffSegment::HammerOnPullOffSegment(HammerOnPullOff* sp)
46 57 : : SlurSegment(sp, ElementType::HAMMER_ON_PULL_OFF_SEGMENT)
47 : {
48 57 : }
49 :
50 0 : HammerOnPullOffSegment::HammerOnPullOffSegment(const HammerOnPullOffSegment& other)
51 0 : : SlurSegment(other)
52 : {
53 0 : }
54 :
55 0 : Color HammerOnPullOffSegment::curColor(const rendering::PaintOptions& opt) const
56 : {
57 0 : if (!opt.isPrinting && MScore::warnGuitarBends && !isValid()) {
58 0 : return selected() ? configuration()->criticalSelectedColor() : configuration()->criticalColor();
59 : }
60 :
61 0 : return SlurSegment::curColor(opt);
62 : }
63 :
64 0 : void HammerOnPullOffSegment::scanElements(std::function<void(EngravingItem*)> func)
65 : {
66 0 : for (HammerOnPullOffText* hopo : m_hopoText) {
67 0 : hopo->scanElements(func);
68 : }
69 :
70 0 : EngravingItem::scanElements(func);
71 0 : }
72 :
73 120 : void HammerOnPullOffSegment::setTrack(track_idx_t idx)
74 : {
75 120 : m_track = idx;
76 134 : for (HammerOnPullOffText* hopo : m_hopoText) {
77 14 : hopo->setTrack(idx);
78 : }
79 120 : }
80 :
81 57 : void HammerOnPullOffSegment::setSelected(bool f)
82 : {
83 57 : for (HammerOnPullOffText* text : m_hopoText) {
84 0 : text->setSelected(f);
85 : }
86 :
87 57 : SlurTieSegment::setSelected(f);
88 57 : }
89 :
90 61 : void HammerOnPullOffSegment::updateHopoText()
91 : {
92 61 : Chord* startChord = nullptr;
93 61 : if (isSingleBeginType()) {
94 61 : EngravingItem* startEl = hammerOnPullOff()->startElement();
95 61 : startChord = startEl && startEl->isChord() ? toChord(startEl) : nullptr;
96 : } else {
97 0 : ChordRest* firstCR = system()->firstChordRest(track());
98 0 : startChord = firstCR && firstCR->isChord() ? toChord(firstCR) : nullptr;
99 : }
100 :
101 61 : Chord* endChord = nullptr;
102 61 : if (isSingleEndType()) {
103 61 : EngravingItem* endEl = hammerOnPullOff()->endElement();
104 61 : endChord = endEl && endEl->isChord() ? toChord(endEl) : nullptr;
105 : } else {
106 : // If the segment doesn't end in this system, the endChord is the first chord of next system
107 0 : ChordRest* lastCR = system()->lastChordRest(track());
108 0 : if (lastCR) {
109 0 : lastCR = toChordRest(lastCR->segment()->next1WithElemsOnTrack(track())->element(track()));
110 : }
111 0 : endChord = lastCR && lastCR->isChord() ? toChord(lastCR) : nullptr;
112 : }
113 :
114 61 : if (!startChord || !endChord) {
115 0 : muse::DeleteAll(m_hopoText);
116 0 : m_hopoText.clear();
117 0 : return;
118 : }
119 :
120 61 : std::vector<HopoTextRegion> hopoTextRegions = computeHopoTextRegions(startChord, endChord);
121 61 : size_t regionCount = hopoTextRegions.size();
122 :
123 61 : size_t curRegionIdx = 0;
124 136 : for (; curRegionIdx < regionCount; ++curRegionIdx) {
125 75 : HopoTextRegion curRegion = hopoTextRegions[curRegionIdx];
126 :
127 : HammerOnPullOffText* curHopoText;
128 75 : if (curRegionIdx < m_hopoText.size()) {
129 : // Reuse existing, if available
130 10 : curHopoText = m_hopoText[curRegionIdx];
131 : } else {
132 : // Create new
133 65 : curHopoText = new HammerOnPullOffText(this);
134 65 : m_hopoText.push_back(curHopoText);
135 : }
136 :
137 75 : curHopoText->setTrack(track());
138 75 : curHopoText->setIsValid(curRegion.isValid);
139 75 : curHopoText->setIsHammerOn(curRegion.isHammerOn);
140 75 : curHopoText->setXmlText(style().styleB(Sid::hopoUpperCase) ? (curRegion.isHammerOn ? "H" : "P") : (curRegion.isHammerOn ? "h" : "p"));
141 75 : curHopoText->setStartChord(curRegion.startChord);
142 75 : curHopoText->setEndChord(curRegion.endChord);
143 : }
144 :
145 : // Delete unused
146 61 : if (curRegionIdx < m_hopoText.size()) {
147 0 : size_t unusedHopoCount = m_hopoText.size() - curRegionIdx;
148 0 : for (size_t i = 0; i < unusedHopoCount; ++i) {
149 0 : delete m_hopoText.back();
150 0 : m_hopoText.pop_back();
151 : }
152 : }
153 61 : }
154 :
155 61 : std::vector<HammerOnPullOffSegment::HopoTextRegion> HammerOnPullOffSegment::computeHopoTextRegions(Chord* startChord, Chord* endChord)
156 : {
157 61 : std::vector<HopoTextRegion> result;
158 61 : bool isTabStaff = staffType()->isTabStaff();
159 61 : if ((isTabStaff && !style().styleB(Sid::hopoShowOnTabStaves)) || (!isTabStaff && !style().styleB(Sid::hopoShowOnStandardStaves))) {
160 0 : return result;
161 : }
162 :
163 136 : for (Chord* curChord = startChord; curChord != endChord;) {
164 75 : IF_ASSERT_FAILED(curChord->tick() <= endChord->tick()) {
165 0 : break;
166 : }
167 :
168 75 : Chord* nextChord = curChord->next();
169 75 : if (!nextChord) {
170 0 : break;
171 : }
172 :
173 75 : Note* curNote = nullptr;
174 75 : Note* nextNote = nullptr;
175 75 : resolveStartEndNotes(&curNote, &nextNote, curChord, nextChord, isTabStaff);
176 :
177 75 : bool isValid = isTabStaff ? nextNote->string() == curNote->string() : nextNote->pitch() != curNote->pitch();
178 75 : bool isHammerOn = isTabStaff ? nextNote->fret() > curNote->fret() : nextNote->pitch() > curNote->pitch();
179 :
180 75 : bool startNewRegion = result.empty() || style().styleB(Sid::hopoShowAll) || result.back().isHammerOn != isHammerOn;
181 :
182 75 : if (startNewRegion) {
183 75 : HopoTextRegion region;
184 75 : region.startChord = curChord;
185 75 : region.endChord = nextChord;
186 75 : region.isValid = isValid;
187 75 : region.isHammerOn = isHammerOn;
188 75 : result.push_back(region);
189 : } else {
190 0 : HopoTextRegion& curRegion = result.back();
191 0 : curRegion.endChord = nextChord;
192 0 : curRegion.isValid = curRegion.isValid && isValid;
193 : }
194 :
195 75 : curChord = nextChord;
196 : }
197 :
198 61 : return result;
199 0 : }
200 :
201 75 : void HammerOnPullOffSegment::resolveStartEndNotes(Note** startNote, Note** endNote, Chord* startChord, Chord* endChord, bool isTabStaff)
202 : {
203 : // In future we need the ability to draw slurs between individual notes. For now, slurs are
204 : // attached to chords so we just try to guess which notes to consider for this HOPO
205 :
206 75 : const std::vector<Note*>& startNotes = startChord->notes();
207 75 : const std::vector<Note*>& endNotes = endChord->notes();
208 75 : int startNotesSize = static_cast<int>(startNotes.size());
209 75 : int endNotesSize = static_cast<int>(endNotes.size());
210 75 : int largerNoteCount = startNotesSize > endNotesSize ? startNotesSize : endNotesSize;
211 :
212 75 : for (int i = largerNoteCount - 1; i >= 0; --i) {
213 75 : int startNoteIdx = std::min(i, startNotesSize - 1);
214 75 : int endNoteIdx = std::min(i, endNotesSize - 1);
215 75 : Note* sNote = startNotes[startNoteIdx];
216 75 : Note* eNote = endNotes[endNoteIdx];
217 75 : if (sNote->tieFor() && sNote->tieFor() == eNote->tieBack()) {
218 0 : continue;
219 : }
220 :
221 75 : bool usablePair = sNote->pitch() != eNote->pitch();
222 75 : if (isTabStaff) {
223 12 : usablePair &= sNote->string() == eNote->string();
224 : }
225 :
226 75 : if (usablePair) {
227 75 : *startNote = sNote;
228 75 : *endNote = eNote;
229 75 : return;
230 : }
231 : }
232 :
233 : // Couldn't find a usable pair
234 0 : *startNote = startChord->upNote();
235 0 : *endNote = endChord->upNote();
236 : }
237 :
238 48 : bool HammerOnPullOffSegment::isUserModified() const
239 : {
240 48 : for (const HammerOnPullOffText* hopoText : m_hopoText) {
241 48 : if (hopoText->isUserModified()) {
242 48 : return true;
243 : }
244 : }
245 :
246 0 : return SlurSegment::isUserModified();
247 : }
248 :
249 0 : bool HammerOnPullOffSegment::isValid() const
250 : {
251 0 : for (const HammerOnPullOffText* hopoText : m_hopoText) {
252 0 : if (!hopoText->isValid()) {
253 0 : return false;
254 : }
255 : }
256 :
257 0 : return true;
258 : }
259 :
260 0 : void HammerOnPullOffSegment::reset()
261 : {
262 0 : for (HammerOnPullOffText* hopoText : m_hopoText) {
263 0 : hopoText->reset();
264 : }
265 :
266 0 : SlurTieSegment::reset();
267 0 : }
268 :
269 : static ElementStyle hopoStyle;
270 :
271 69 : HammerOnPullOffText::HammerOnPullOffText(HammerOnPullOffSegment* parent)
272 0 : : TextBase(ElementType::HAMMER_ON_PULL_OFF_TEXT, parent, TextStyleType::HAMMER_ON_PULL_OFF,
273 69 : ElementFlag::MOVABLE | ElementFlag::GENERATED | ElementFlag::ON_STAFF)
274 : {
275 69 : resetProperty(Pid::PLACEMENT);
276 69 : initElementStyle(&hopoStyle);
277 69 : }
278 :
279 0 : HammerOnPullOffText::HammerOnPullOffText(const HammerOnPullOffText& h)
280 0 : : TextBase(h)
281 : {
282 0 : }
283 :
284 0 : std::vector<LineF> HammerOnPullOffText::dragAnchorLines() const
285 : {
286 0 : std::vector<LineF> result;
287 :
288 0 : PointF p1 = canvasPos();
289 :
290 0 : HammerOnPullOffSegment* hopoSeg = hopoSegment();
291 0 : const Shape& hopoSegShape = hopoSeg->ldata()->shape();
292 0 : double x = ldata()->pos().x();// + hopoSegShape.bbox().x();
293 0 : double y = hopoSeg->hammerOnPullOff()->up() ? hopoSegShape.topAtX(x) : hopoSegShape.bottomAtX(x);
294 :
295 0 : PointF p2 = PointF(x, y) + hopoSeg->canvasPos();
296 :
297 0 : result.push_back(LineF(p1, p2));
298 :
299 0 : return result;
300 0 : }
301 :
302 103 : bool HammerOnPullOffText::isUserModified() const
303 : {
304 721 : for (const TextStyleProperty& p : *textStyle(textStyleType())) {
305 721 : if (getProperty(p.pid) != propertyDefault(p.pid)) {
306 103 : return true;
307 : }
308 : }
309 :
310 0 : return TextBase::isUserModified();
311 : }
312 :
313 0 : Color HammerOnPullOffText::curColor(const rendering::PaintOptions& opt) const
314 : {
315 0 : if (!isValid() && MScore::warnGuitarBends && !opt.isPrinting) {
316 0 : return selected() ? configuration()->criticalSelectedColor() : configuration()->criticalColor();
317 : }
318 :
319 0 : return TextBase::curColor(opt);
320 : }
321 :
322 1340 : PropertyValue HammerOnPullOffText::propertyDefault(Pid id) const
323 : {
324 1340 : switch (id) {
325 124 : case Pid::PLACEMENT:
326 124 : return PlacementV::ABOVE;
327 1216 : default:
328 1216 : return TextBase::propertyDefault(id);
329 : }
330 : }
331 : } // namespace mu::engraving
|