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) 2022 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 "harppedaldiagram.h"
24 :
25 : #include "translation.h"
26 : #include "types/typesconv.h"
27 :
28 : #include "../editing/editharppedaldiagram.h"
29 :
30 : #include "part.h"
31 : #include "score.h"
32 : #include "segment.h"
33 :
34 : #include "log.h"
35 :
36 : using namespace mu;
37 : using namespace mu::engraving;
38 :
39 : // Position of separator character in diagram
40 : static constexpr size_t SEPARATOR_IDX = 3;
41 :
42 93 : static const String harpStringTypeToString(HarpStringType type)
43 : {
44 93 : switch (type) {
45 14 : case HarpStringType::A:
46 14 : return String(u"A");
47 12 : case HarpStringType::B:
48 12 : return String(u"B");
49 14 : case HarpStringType::C:
50 14 : return String(u"C");
51 15 : case HarpStringType::D:
52 15 : return String(u"D");
53 11 : case HarpStringType::E:
54 11 : return String(u"E");
55 14 : case HarpStringType::F:
56 14 : return String(u"F");
57 13 : case HarpStringType::G:
58 13 : return String(u"G");
59 : }
60 0 : return String();
61 : }
62 :
63 : // STYLE
64 : static const ElementStyle harpPedalDiagramStyle {
65 : { Sid::harpPedalDiagramPlacement, Pid::PLACEMENT },
66 : { Sid::harpPedalDiagramMinDistance, Pid::MIN_DISTANCE },
67 : };
68 :
69 : static const ElementStyle harpPedalTextDiagramStyle {
70 : { Sid::harpPedalTextDiagramPlacement, Pid::PLACEMENT },
71 : { Sid::harpPedalTextDiagramMinDistance, Pid::MIN_DISTANCE },
72 : };
73 :
74 : // HarpPedalDiagram
75 157 : void HarpPedalDiagram::setPlayableTpcs()
76 : {
77 157 : std::set<int> playableTpcs;
78 : static const std::map<HarpStringType, int> string2step = {
79 : { HarpStringType::C, 0 },
80 : { HarpStringType::D, 1 },
81 : { HarpStringType::E, 2 },
82 : { HarpStringType::F, 3 },
83 : { HarpStringType::G, 4 },
84 : { HarpStringType::A, 5 },
85 : { HarpStringType::B, 6 }
86 163 : };
87 : static const std::map<PedalPosition, AccidentalVal> position2accidentalVal = {
88 : { PedalPosition::FLAT, AccidentalVal::FLAT },
89 : { PedalPosition::NATURAL, AccidentalVal::NATURAL },
90 : { PedalPosition::SHARP, AccidentalVal::SHARP }
91 163 : };
92 :
93 2512 : for (size_t i = 0; i < m_pedalState.size(); i++) {
94 1099 : int stringStep = string2step.at(HarpStringType(i));
95 1099 : AccidentalVal accidentalVal = position2accidentalVal.at(m_pedalState[i]);
96 1099 : int resTpc = step2tpc(stringStep, accidentalVal);
97 1099 : playableTpcs.insert(resTpc);
98 : }
99 :
100 157 : m_playableTpcs = playableTpcs;
101 157 : }
102 :
103 22 : HarpPedalDiagram::HarpPedalDiagram(DummyParentOr<Segment> parent)
104 22 : : TextBase(ElementType::HARP_DIAGRAM, parent, TextStyleType::HARP_PEDAL_DIAGRAM, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
105 : {
106 22 : initElementStyle(&harpPedalDiagramStyle);
107 : m_pedalState
108 22 : = std::array<PedalPosition, HARP_STRING_NO> { PedalPosition::NATURAL, PedalPosition::NATURAL, PedalPosition::NATURAL,
109 : PedalPosition::NATURAL,
110 : PedalPosition::NATURAL, PedalPosition::NATURAL, PedalPosition::NATURAL };
111 22 : setPlayableTpcs();
112 22 : }
113 :
114 0 : HarpPedalDiagram::HarpPedalDiagram(const HarpPedalDiagram& h)
115 0 : : TextBase(h)
116 : {
117 0 : m_pedalState = h.m_pedalState;
118 0 : m_isDiagram = h.m_isDiagram;
119 0 : setPlayableTpcs();
120 0 : }
121 :
122 6 : void HarpPedalDiagram::setPedalState(std::array<PedalPosition, HARP_STRING_NO> state)
123 : {
124 6 : m_pedalState = state;
125 6 : setPlayableTpcs();
126 6 : }
127 :
128 18 : void HarpPedalDiagram::setIsDiagram(bool diagram)
129 : {
130 18 : m_isDiagram = diagram;
131 18 : const ElementStyle& styleMap = m_isDiagram ? harpPedalDiagramStyle : harpPedalTextDiagramStyle;
132 :
133 18 : if (m_isDiagram) {
134 3 : setTextStyleType(TextStyleType::HARP_PEDAL_DIAGRAM);
135 : } else {
136 15 : setTextStyleType(TextStyleType::HARP_PEDAL_TEXT_DIAGRAM);
137 : }
138 :
139 18 : initElementStyle(&styleMap);
140 18 : }
141 :
142 116 : void HarpPedalDiagram::setPedal(HarpStringType harpString, PedalPosition pedal)
143 : {
144 116 : m_pedalState.at(harpString) = pedal;
145 116 : setPlayableTpcs();
146 116 : }
147 :
148 32 : String HarpPedalDiagram::createDiagramText()
149 : {
150 32 : String diagram;
151 32 : if (m_isDiagram) {
152 144 : for (size_t idx = 0; idx < m_pedalState.size(); idx++) {
153 63 : if (idx == SEPARATOR_IDX) {
154 : // insert separator
155 9 : diagram.append(u"<sym>harpPedalDivider</sym>");
156 : }
157 63 : switch (m_pedalState[idx]) {
158 12 : case PedalPosition::FLAT:
159 12 : diagram.append(u"<sym>harpPedalRaised</sym>");
160 12 : break;
161 36 : case PedalPosition::NATURAL:
162 36 : diagram.append(u"<sym>harpPedalCentered</sym>");
163 36 : break;
164 15 : case PedalPosition::SHARP:
165 15 : diagram.append(u"<sym>harpPedalLowered</sym>");
166 15 : break;
167 0 : case PedalPosition::UNSET:
168 0 : break;
169 : }
170 : }
171 : } else {
172 : // find difference between pedal state of previous diagram and this one
173 : // if no previous, the pedals are "unset" meaning the whole diagram will be displayed
174 :
175 23 : constexpr std::array<PedalPosition, HARP_STRING_NO> initState
176 : = { PedalPosition::UNSET, PedalPosition::UNSET, PedalPosition::UNSET, PedalPosition::UNSET, PedalPosition::UNSET,
177 : PedalPosition::UNSET, PedalPosition::UNSET };
178 : std::array<PedalPosition, HARP_STRING_NO> prevState;
179 23 : HarpPedalDiagram* prevDiagram = nullptr;
180 23 : if (originPart() && segment()) {
181 23 : prevDiagram = originPart()->prevHarpDiagram(segment()->tick());
182 : }
183 :
184 23 : if (prevDiagram != nullptr) {
185 : // If states are the same work backwards until we find the first difference then calculate from there
186 : // This ensures the diagram is identical to the previous one
187 14 : prevState = prevDiagram->getPedalState();
188 16 : while (prevState == m_pedalState) {
189 : // if states are the same but previous is a diagram write whole config out
190 2 : if (prevDiagram->isDiagram()) {
191 0 : prevState = initState;
192 0 : break;
193 : }
194 : // Check if there's a diagram before. If not, start of score and use init state
195 2 : prevDiagram = originPart()->prevHarpDiagram(prevDiagram->segment()->tick());
196 2 : if (prevDiagram != nullptr) {
197 2 : prevState = prevDiagram->getPedalState();
198 : } else {
199 : // set to init state and break loop
200 0 : prevState = initState;
201 0 : break;
202 : }
203 : }
204 : } else {
205 9 : prevState = initState;
206 : }
207 :
208 23 : String topLine, bottomLine;
209 :
210 368 : for (size_t idx = 0; idx < m_pedalState.size(); idx++) {
211 161 : if (m_pedalState[idx] != prevState[idx]) {
212 93 : String strName = harpStringTypeToString(HarpStringType(idx));
213 93 : switch (m_pedalState[idx]) {
214 21 : case PedalPosition::FLAT:
215 21 : strName.append(u"<sym>csymAccidentalFlat</sym> ");
216 21 : break;
217 55 : case PedalPosition::NATURAL:
218 55 : strName.append(u"<sym>csymAccidentalNatural</sym> ");
219 55 : break;
220 17 : case PedalPosition::SHARP:
221 17 : strName.append(u"<sym>csymAccidentalSharp</sym> ");
222 17 : break;
223 0 : case PedalPosition::UNSET:
224 0 : break;
225 : }
226 93 : if (idx < SEPARATOR_IDX) {
227 41 : bottomLine.append(strName);
228 : } else {
229 52 : topLine.append(strName);
230 : }
231 93 : }
232 : }
233 :
234 23 : if (topLine.size() == 0) {
235 0 : diagram.append(bottomLine);
236 : } else {
237 23 : diagram.append(topLine + u"\n" + bottomLine);
238 : }
239 23 : }
240 :
241 32 : return diagram;
242 0 : }
243 :
244 32 : void HarpPedalDiagram::updateDiagramText()
245 : {
246 32 : undoChangeProperty(Pid::TEXT, createDiagramText(), PropertyFlags::STYLED);
247 32 : }
248 :
249 0 : void HarpPedalDiagram::undoChangePedalState(std::array<PedalPosition, HARP_STRING_NO> _pedalState)
250 : {
251 0 : const std::list<EngravingObject*> links = linkList();
252 0 : for (EngravingObject* obj : links) {
253 0 : if (!obj || !obj->isHarpPedalDiagram()) {
254 0 : continue;
255 : }
256 :
257 0 : HarpPedalDiagram* item = toHarpPedalDiagram(obj);
258 0 : Score* linkedScore = item->score();
259 0 : if (!linkedScore) {
260 0 : continue;
261 : }
262 :
263 0 : linkedScore->undo(new ChangeHarpPedalState(item, _pedalState));
264 : }
265 0 : }
266 :
267 3 : bool HarpPedalDiagram::isTpcPlayable(int tpc)
268 : {
269 3 : return m_playableTpcs.find(tpc) != m_playableTpcs.cend();
270 : }
271 :
272 564 : PropertyValue HarpPedalDiagram::getProperty(Pid propertyId) const
273 : {
274 564 : switch (propertyId) {
275 12 : case Pid::HARP_IS_DIAGRAM:
276 12 : return m_isDiagram;
277 552 : default:
278 552 : return TextBase::getProperty(propertyId);
279 : }
280 : }
281 :
282 1000 : bool HarpPedalDiagram::setProperty(Pid propertyId, const PropertyValue& v)
283 : {
284 1000 : switch (propertyId) {
285 0 : case Pid::HARP_IS_DIAGRAM:
286 0 : setIsDiagram(v.toBool());
287 0 : break;
288 1000 : default:
289 1000 : if (!TextBase::setProperty(propertyId, v)) {
290 0 : return false;
291 : }
292 : }
293 1000 : return true;
294 : }
295 :
296 235 : PropertyValue HarpPedalDiagram::propertyDefault(Pid id) const
297 : {
298 235 : switch (id) {
299 12 : case Pid::TEXT_STYLE:
300 12 : return TextStyleType::HARP_PEDAL_DIAGRAM;
301 12 : case Pid::HARP_IS_DIAGRAM:
302 12 : return true;
303 211 : default:
304 211 : return TextBase::propertyDefault(id);
305 : }
306 : }
307 :
308 0 : String HarpPedalDiagram::accessibleInfo() const
309 : {
310 0 : String rez = score() ? score()->getTextStyleUserName(textStyleType()).translated() : TConv::translatedUserName(textStyleType());
311 0 : String s;
312 :
313 0 : for (size_t idx = 0; idx < m_pedalState.size(); idx++) {
314 0 : s.append(harpStringTypeToString(HarpStringType(idx)));
315 0 : switch (m_pedalState[idx]) {
316 0 : case PedalPosition::FLAT:
317 0 : s.append(u"♭ ");
318 0 : break;
319 0 : case PedalPosition::NATURAL:
320 0 : s.append(u"♮ ");
321 0 : break;
322 0 : case PedalPosition::SHARP:
323 0 : s.append(u"♯ ");
324 0 : break;
325 0 : case PedalPosition::UNSET:
326 0 : s.append(u" unset ");
327 0 : break;
328 : }
329 : }
330 :
331 : // trim trailing space
332 0 : s.truncate(s.size() - 1);
333 :
334 0 : return String(u"%1: %2").arg(rez, s);
335 0 : }
336 :
337 0 : String HarpPedalDiagram::screenReaderInfo() const
338 : {
339 0 : String rez = score() ? score()->getTextStyleUserName(textStyleType()).translated() : TConv::translatedUserName(textStyleType());
340 0 : String s;
341 :
342 0 : for (size_t idx = 0; idx < m_pedalState.size(); idx++) {
343 0 : s.append(harpStringTypeToString(HarpStringType(idx)) + u" ");
344 0 : switch (m_pedalState.at(idx)) {
345 0 : case PedalPosition::FLAT:
346 0 : s.append(muse::mtrc("engraving", TConv::userName(AccidentalVal::FLAT, true)));
347 0 : break;
348 0 : case PedalPosition::NATURAL:
349 0 : s.append(muse::mtrc("engraving", TConv::userName(AccidentalVal::NATURAL, true)));
350 0 : break;
351 0 : case PedalPosition::SHARP:
352 0 : s.append(muse::mtrc("engraving", TConv::userName(AccidentalVal::SHARP, true)));
353 0 : break;
354 0 : case PedalPosition::UNSET:
355 0 : s.append(u" unset ");
356 0 : break;
357 : }
358 : }
359 :
360 0 : return String(u"%1: %2").arg(rez, s);
361 0 : }
|