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 "shadownote.h"
24 :
25 : #include "draw/types/pen.h"
26 :
27 : #include "accidental.h"
28 : #include "articulation.h"
29 : #include "hook.h"
30 : #include "measure.h"
31 : #include "rest.h"
32 : #include "score.h"
33 : #include "stafftype.h"
34 :
35 : using namespace mu;
36 : using namespace muse::draw;
37 :
38 : namespace mu::engraving {
39 : //---------------------------------------------------------
40 : // ShadowNote
41 : //---------------------------------------------------------
42 :
43 2334 : ShadowNote::ShadowNote(Score* s)
44 2334 : : EngravingItem(ElementType::SHADOW_NOTE, s), m_noteheadSymbol(SymId::noSym)
45 : {
46 2334 : m_lineIndex = 1000;
47 2334 : m_duration = TDuration(DurationType::V_INVALID);
48 2334 : m_isRest = false;
49 2334 : }
50 :
51 0 : bool ShadowNote::isValid() const
52 : {
53 0 : return m_noteheadSymbol != SymId::noSym;
54 : }
55 :
56 0 : void ShadowNote::setState(SymId noteSymbol, TDuration duration, bool rest, bool beyondScore, AccidentalType accidentalType,
57 : const std::set<SymId>& articulationIds)
58 : {
59 0 : m_noteheadSymbol = noteSymbol;
60 0 : m_duration = duration;
61 0 : m_isRest = rest;
62 0 : m_beyondScore = beyondScore;
63 0 : m_accidentalType = accidentalType;
64 0 : m_articulationIds = articulationIds;
65 0 : }
66 :
67 0 : bool ShadowNote::hasStem() const
68 : {
69 0 : return !m_isRest && m_duration.hasStem();
70 : }
71 :
72 0 : bool ShadowNote::hasFlag() const
73 : {
74 0 : return hasStem() && m_duration.hooks() > 0;
75 : }
76 :
77 0 : SymId ShadowNote::flagSym() const
78 : {
79 0 : if (!hasStem()) {
80 0 : return SymId::noSym;
81 : }
82 :
83 0 : bool straight = style().styleB(Sid::useStraightNoteFlags);
84 0 : int hooks = computeUp() ? m_duration.hooks() : -m_duration.hooks();
85 0 : return Hook::symIdForHookIndex(hooks, straight);
86 : }
87 :
88 0 : AccidentalType ShadowNote::accidentalType() const
89 : {
90 0 : return m_accidentalType;
91 : }
92 :
93 0 : const std::set<SymId>& ShadowNote::articulationIds() const
94 : {
95 0 : return m_articulationIds;
96 : }
97 :
98 0 : bool ShadowNote::ledgerLinesVisible() const
99 : {
100 0 : return !m_isRest && m_lineIndex < 100 && m_lineIndex > -100;
101 : }
102 :
103 : //---------------------------------------------------------
104 : // computeUp
105 : //---------------------------------------------------------
106 :
107 0 : bool ShadowNote::computeUp() const
108 : {
109 0 : const StaffType* staffType = this->staffType();
110 0 : bool tabStaff = staffType && staffType->isTabStaff();
111 :
112 0 : if (tabStaff && (staffType->stemless() || !staffType->stemThrough())) {
113 : // if TAB staff with no stems or stem beside staff
114 0 : return true;
115 : }
116 :
117 0 : if (score()->tick2measure(m_tick)->hasVoices(staffIdx()) || voice() != 0) {
118 0 : return !(voice() & 1);
119 : } else {
120 0 : return m_lineIndex > (staffType ? staffType->middleLine() : 4);
121 : }
122 : }
123 :
124 : // draw articulations, laying them out in a fashion similar to
125 : // ChordLayout::layoutArticulations
126 0 : void ShadowNote::drawArticulations(Painter* painter) const
127 : {
128 : // Approximate bounding box of note, including stem, relative to notehead
129 0 : double noteheadWidth = symWidth(m_noteheadSymbol);
130 0 : double noteheadHeight = symHeight(m_noteheadSymbol);
131 0 : double ms = spatium();
132 0 : double x1 = noteheadWidth * .5 - (ms * mag());
133 0 : double x2 = x1 + 2 * ms * mag();
134 0 : bool up = !computeUp();
135 0 : double y01 = -noteheadHeight * .5;
136 0 : double y02 = -y01;
137 0 : if (hasStem()) {
138 : // Stem set at 3.5 spatium in SingleDraw::draw(const ShadowNote* item ...
139 0 : if (up) {
140 0 : y02 = ms * 3.5;
141 : } else {
142 0 : y01 = -ms * 3.5;
143 : }
144 : }
145 0 : RectF boundRect = RectF(PointF(x1, y01), PointF(x2, y02));
146 :
147 : // Place articulations kept close to the note first, near the notehead
148 : // Articulations close to the note are defined in Articulation::layoutCloseToNote
149 0 : bool wasStaccato = false; // keep track for special accent-staccato kerning
150 0 : SymId prevArticulation = mu::engraving::SymId::noSym;
151 0 : for (const SymId& artic: m_articulationIds) {
152 0 : String articName = Articulation::symId2ArticulationName(artic);
153 0 : if (articName == u"staccato") {
154 0 : wasStaccato = true;
155 0 : drawCloseArticulation(painter, artic, boundRect, up, prevArticulation);
156 0 : prevArticulation = artic;
157 : } else {
158 0 : if (articName == u"tenuto") {
159 0 : drawCloseArticulation(painter, artic, boundRect, up, prevArticulation);
160 0 : prevArticulation = artic;
161 : }
162 0 : wasStaccato = false;
163 : }
164 0 : }
165 :
166 : // Adjust bounding box to include the whole of the staff segment,
167 : // then place articulations not closeToNote
168 0 : ms *= .5;
169 0 : double y1 = -m_lineIndex * ms;
170 0 : double y2 = (this->staffType()->bottomLine() - m_lineIndex) * ms;
171 0 : if (boundRect.y() > y1) {
172 0 : boundRect.setTop(y1);
173 0 : if (up) {
174 0 : wasStaccato = false;
175 : }
176 : }
177 0 : if (boundRect.bottom() < y2) {
178 0 : boundRect.setBottom(y2);
179 0 : if (!up) {
180 0 : wasStaccato = false;
181 : }
182 : }
183 :
184 0 : for (const SymId& artic: m_articulationIds) {
185 0 : String articName = Articulation::symId2ArticulationName(artic);
186 0 : if (articName == u"staccato" || articName == u"tenuto") {
187 0 : continue;
188 : } else {
189 0 : if (articName.contains(u"marcato")) {
190 : // Marcato always above staff
191 0 : drawFarArticulation(painter, artic, boundRect, true, false);
192 0 : } else if (articName == u"accent") {
193 : // Accents get special kerning if adjacent to staccato
194 0 : drawFarArticulation(painter, artic, boundRect, up, wasStaccato);
195 : } else {
196 : // Other articulations (e.g., laissez-vibrer)
197 0 : drawFarArticulation(painter, artic, boundRect, up, false);
198 : }
199 0 : wasStaccato = false;
200 : }
201 0 : }
202 0 : }
203 :
204 0 : void ShadowNote::drawCloseArticulation(muse::draw::Painter* painter, const SymId& artic, RectF& boundRect, bool up,
205 : const SymId& prevArticulation) const
206 : {
207 0 : PointF coord;
208 0 : double spacing = spatium();
209 0 : SymId articSym = artic;
210 0 : double articHeight = symHeight(articSym);
211 :
212 : // Center the articulation on the note-head
213 0 : coord.rx() = boundRect.x() + (boundRect.width() - symWidth(artic)) / 2;
214 :
215 : // Place articulation above or below boundRect and adjust boundRect accordingly
216 0 : double minDist = 0.4 * spacing;
217 0 : if (up) {
218 : // If operating inside the staff, center articulation between lines
219 0 : if (m_lineIndex > 1) {
220 0 : coord.ry() = (articHeight - spacing * (3 - (m_lineIndex & 1))) / 2;
221 0 : if (prevArticulation != mu::engraving::SymId::noSym) {
222 0 : if (m_lineIndex > 2) {
223 0 : coord.ry() -= spacing;
224 : } else {
225 0 : coord.ry() -= minDist + symHeight(prevArticulation);
226 : }
227 : }
228 : } else {
229 0 : double topY = boundRect.y();
230 0 : coord.ry() = topY - minDist;
231 : }
232 0 : boundRect.setTop(coord.ry() - articHeight);
233 : } else {
234 0 : if (m_lineIndex < this->staffType()->bottomLine() - 1) {
235 0 : coord.ry() = (articHeight + spacing * (3 - (m_lineIndex & 1))) / 2;
236 0 : if (prevArticulation != mu::engraving::SymId::noSym) {
237 0 : if (m_lineIndex < this->staffType()->bottomLine() - 2) {
238 0 : coord.ry() += spacing;
239 : } else {
240 0 : coord.ry() += minDist + symHeight(prevArticulation);
241 : }
242 : }
243 : } else {
244 0 : double bottomY = boundRect.bottom();
245 0 : coord.ry() = bottomY + minDist + articHeight;
246 : }
247 0 : boundRect.setBottom(coord.ry());
248 : }
249 :
250 0 : drawSymbol(articSym, painter, coord);
251 0 : }
252 :
253 0 : void ShadowNote::drawFarArticulation(muse::draw::Painter* painter, const SymId& artic, RectF& boundRect, bool up,
254 : bool accentStaccatoKern) const
255 : {
256 0 : PointF coord;
257 0 : double spacing = spatium();
258 0 : SymId articSym = artic;
259 0 : double articHeight = symHeight(articSym);
260 :
261 : // If a LaissezVibrer, place beside the note and widen the bounding box
262 0 : if (articSym == SymId::articLaissezVibrerAbove || articSym == SymId::articLaissezVibrerBelow) {
263 0 : articSym = up ? SymId::articLaissezVibrerAbove : SymId::articLaissezVibrerBelow;
264 0 : const int upDir = up ? -1 : 1;
265 0 : const double noteHeight = symHeight(m_noteheadSymbol);
266 :
267 0 : double center = 0.5 * symWidth(m_noteheadSymbol);
268 0 : double visualInset = 0.1 * spacing;
269 :
270 0 : coord.rx() = center + visualInset - symBbox(articSym).left();
271 0 : coord.ry() = (noteHeight / 2 + 0.2 * spacing) * upDir;
272 0 : boundRect.setWidth(boundRect.width() + symWidth(articSym));
273 0 : } else {
274 : // If on the stem side of a note (e.g., marcato on a low note), move artic over for stem,
275 : // else, center it on the notehead;
276 0 : coord.rx() = boundRect.x() + (boundRect.width() - symWidth(artic)) / 2;
277 0 : if (up == computeUp() && hasStem()) {
278 0 : coord.rx() += 0.25 * symWidth(m_noteheadSymbol);
279 : }
280 :
281 : // Place articulation above or below boundRect and adjust boundRect accordingly
282 0 : if (up) {
283 0 : double topY = boundRect.y();
284 0 : coord.ry() = topY - (accentStaccatoKern ? 0.2 : 0.4) * spacing;
285 0 : boundRect.setTop(coord.ry() - articHeight);
286 : } else {
287 0 : double bottomY = boundRect.bottom();
288 0 : coord.ry() = bottomY + (accentStaccatoKern ? 0.2 : 0.4) * spacing + articHeight;
289 0 : boundRect.setBottom(coord.ry());
290 : }
291 : }
292 :
293 0 : drawSymbol(articSym, painter, coord);
294 0 : }
295 : }
|