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 "ambitus.h"
24 :
25 : #include "translation.h"
26 :
27 : #include "accidental.h"
28 : #include "chord.h"
29 : #include "factory.h"
30 : #include "measure.h"
31 : #include "note.h"
32 : #include "score.h"
33 : #include "segment.h"
34 : #include "staff.h"
35 : #include "system.h"
36 : #include "utils.h"
37 :
38 : #include "log.h"
39 :
40 : using namespace mu;
41 : using namespace mu::engraving;
42 :
43 : //---------------------------------------------------------
44 : // Ambitus
45 : //---------------------------------------------------------
46 :
47 2 : Ambitus::Ambitus(DummyParentOr<Segment> parent)
48 2 : : EngravingItem(ElementType::AMBITUS, parent, ElementFlag::ON_STAFF)
49 : {
50 2 : m_noteHeadGroup = NOTEHEADGROUP_DEFAULT;
51 2 : m_noteHeadType = NOTEHEADTYPE_DEFAULT;
52 2 : m_direction = DIRECTION_DEFAULT;
53 2 : m_hasLine = HASLINE_DEFAULT;
54 2 : m_lineWidth = LINEWIDTH_DEFAULT;
55 2 : m_topPitch = INVALID_PITCH;
56 2 : m_bottomPitch = INVALID_PITCH;
57 2 : m_topTpc = Tpc::TPC_INVALID;
58 2 : m_bottomTpc = Tpc::TPC_INVALID;
59 :
60 2 : m_topAccidental = Factory::createAccidental(this, false);
61 2 : m_bottomAccidental = Factory::createAccidental(this, false);
62 2 : }
63 :
64 0 : Ambitus::Ambitus(const Ambitus& a)
65 0 : : EngravingItem(a)
66 : {
67 0 : m_noteHeadGroup = a.m_noteHeadGroup;
68 0 : m_noteHeadType = a.m_noteHeadType;
69 0 : m_direction = a.m_direction;
70 0 : m_hasLine = a.m_hasLine;
71 0 : m_lineWidth = a.m_lineWidth;
72 :
73 0 : m_topAccidental = a.m_topAccidental->clone();
74 0 : m_topAccidental->setOwnershipParent(this);
75 :
76 0 : m_bottomAccidental = a.m_bottomAccidental->clone();
77 0 : m_bottomAccidental->setOwnershipParent(this);
78 :
79 0 : m_topPitch = a.m_topPitch;
80 0 : m_topTpc = a.m_topTpc;
81 0 : m_bottomPitch = a.m_bottomPitch;
82 0 : m_bottomTpc = a.m_bottomTpc;
83 0 : }
84 :
85 4 : Ambitus::~Ambitus()
86 : {
87 2 : delete m_topAccidental;
88 2 : delete m_bottomAccidental;
89 4 : }
90 :
91 : //---------------------------------------------------------
92 : // mag
93 : //---------------------------------------------------------
94 :
95 32 : double Ambitus::mag() const
96 : {
97 32 : return staff() ? staff()->staffMag(tick()) : 1.0;
98 : }
99 :
100 : //---------------------------------------------------------
101 : // initFrom
102 : //---------------------------------------------------------
103 :
104 0 : void Ambitus::initFrom(Ambitus* a)
105 : {
106 0 : m_noteHeadGroup = a->m_noteHeadGroup;
107 0 : m_noteHeadType = a->m_noteHeadType;
108 0 : m_direction = a->m_direction;
109 0 : m_hasLine = a->m_hasLine;
110 0 : m_lineWidth = a->m_lineWidth;
111 0 : m_topPitch = a->m_topPitch;
112 0 : m_bottomPitch = a->m_bottomPitch;
113 0 : m_topTpc = a->m_topTpc;
114 0 : m_bottomTpc = a->m_bottomTpc;
115 0 : }
116 :
117 : //---------------------------------------------------------
118 : // setTrack
119 : //
120 : // when the Ambitus element is assigned a track,
121 : // initialize top and bottom 'notes' to top and bottom staff lines
122 : //---------------------------------------------------------
123 :
124 2 : void Ambitus::setTrack(track_idx_t t)
125 : {
126 2 : EngravingItem::setTrack(t);
127 :
128 : // if not initialized and there is a segment and a staff,
129 : // initialize pitches and tpc's to first and last staff line
130 : // (for use in palettes)
131 4 : if (!pitchIsValid(m_topPitch) || !tpcIsValid(m_topTpc)
132 4 : || !pitchIsValid(m_bottomPitch) || !tpcIsValid(m_bottomTpc)) {
133 0 : if (segment() && staff()) {
134 0 : Ambitus::Ranges ranges = estimateRanges();
135 0 : m_topTpc = ranges.topTpc;
136 0 : m_bottomTpc = ranges.bottomTpc;
137 0 : m_topPitch = ranges.topPitch;
138 0 : m_bottomPitch = ranges.bottomPitch;
139 :
140 0 : m_topAccidental->setTrack(t);
141 0 : m_bottomAccidental->setTrack(t);
142 : }
143 : }
144 2 : }
145 :
146 : //---------------------------------------------------------
147 : // setTop/BottomPitch
148 : //
149 : // setting either pitch requires to adjust the corresponding tpc
150 : //---------------------------------------------------------
151 :
152 2 : void Ambitus::setTopPitch(int val, bool applyLogic)
153 : {
154 2 : if (!applyLogic) {
155 2 : m_topPitch = val;
156 2 : return;
157 : }
158 :
159 : // if pitch difference is not an integer number of octaves, adjust tpc
160 : // (to avoid 'wild' tpc changes with octave changes)
161 0 : if ((val - topPitch()) % PITCH_DELTA_OCTAVE != 0) {
162 0 : Key key = (staff() && segment()) ? staff()->key(segment()->tick()) : Key::C;
163 0 : m_topTpc = pitch2tpc(val, key, Prefer::NEAREST);
164 : }
165 0 : m_topPitch = val;
166 0 : normalize();
167 : }
168 :
169 2 : void Ambitus::setBottomPitch(int val, bool applyLogic)
170 : {
171 2 : if (!applyLogic) {
172 2 : m_bottomPitch = val;
173 2 : return;
174 : }
175 :
176 : // if pitch difference is not an integer number of octaves, adjust tpc
177 : // (to avoid 'wild' tpc changes with octave changes)
178 0 : if ((val - bottomPitch()) % PITCH_DELTA_OCTAVE != 0) {
179 0 : Key key = (staff() && segment()) ? staff()->key(segment()->tick()) : Key::C;
180 0 : m_bottomTpc = pitch2tpc(val, key, Prefer::NEAREST);
181 : }
182 0 : m_bottomPitch = val;
183 0 : normalize();
184 : }
185 :
186 : //---------------------------------------------------------
187 : // setTop/BottomTpc
188 : //
189 : // setting either tpc requires to adjust the corresponding pitch
190 : // (but remaining in the same octave)
191 : //---------------------------------------------------------
192 :
193 2 : void Ambitus::setTopTpc(int val, bool applyLogic)
194 : {
195 2 : m_topTpc = val;
196 :
197 2 : if (!applyLogic) {
198 2 : return;
199 : }
200 :
201 0 : int octave = topPitch() / PITCH_DELTA_OCTAVE;
202 0 : int newOctavedPitch = (tpc2pitch(val) + PITCH_DELTA_OCTAVE) % PITCH_DELTA_OCTAVE;
203 0 : m_topPitch = (octave * PITCH_DELTA_OCTAVE) + newOctavedPitch;
204 0 : normalize();
205 : }
206 :
207 2 : void Ambitus::setBottomTpc(int val, bool applyLogic)
208 : {
209 2 : m_bottomTpc = val;
210 :
211 2 : if (!applyLogic) {
212 2 : return;
213 : }
214 :
215 0 : int octave = bottomPitch() / PITCH_DELTA_OCTAVE;
216 0 : int newOctavedPitch = (tpc2pitch(val) + PITCH_DELTA_OCTAVE) % PITCH_DELTA_OCTAVE;
217 0 : m_bottomPitch = (octave * PITCH_DELTA_OCTAVE) + newOctavedPitch;
218 0 : normalize();
219 : }
220 :
221 : //---------------------------------------------------------
222 : // scanElements
223 : //---------------------------------------------------------
224 :
225 0 : void Ambitus::scanElements(std::function<void(EngravingItem*)> func)
226 : {
227 0 : func(this);
228 0 : if (m_topAccidental->accidentalType() != AccidentalType::NONE) {
229 0 : func(m_topAccidental);
230 : }
231 :
232 0 : if (m_bottomAccidental->accidentalType() != AccidentalType::NONE) {
233 0 : func(m_bottomAccidental);
234 : }
235 0 : }
236 :
237 : //---------------------------------------------------------
238 : // noteHead
239 : //---------------------------------------------------------
240 :
241 2 : SymId Ambitus::noteHead() const
242 : {
243 2 : int hg = 1;
244 2 : NoteHeadType ht = NoteHeadType::HEAD_QUARTER;
245 :
246 2 : if (m_noteHeadType != NoteHeadType::HEAD_AUTO) {
247 1 : ht = m_noteHeadType;
248 : }
249 :
250 2 : SymId t = Note::noteHead(hg, m_noteHeadGroup, ht);
251 2 : if (t == SymId::noSym) {
252 0 : LOGD("invalid notehead %d/%d", int(m_noteHeadGroup), int(m_noteHeadType));
253 0 : t = Note::noteHead(0, NoteHeadGroup::HEAD_NORMAL, ht);
254 : }
255 2 : return t;
256 : }
257 :
258 : //---------------------------------------------------------
259 : // headWidth
260 : //
261 : // returns the width of the notehead symbol
262 : //---------------------------------------------------------
263 :
264 2 : double Ambitus::headWidth() const
265 : {
266 2 : return symWidth(noteHead());
267 : }
268 :
269 : //---------------------------------------------------------
270 : // normalize
271 : //
272 : // makes sure topPitch is not < bottomPitch
273 : //---------------------------------------------------------
274 :
275 0 : void Ambitus::normalize()
276 : {
277 0 : if (m_topPitch < m_bottomPitch) {
278 0 : std::swap(m_topPitch, m_bottomPitch);
279 0 : std::swap(m_topTpc, m_bottomTpc);
280 : }
281 0 : }
282 :
283 : //---------------------------------------------------------
284 : // updateRange
285 : //
286 : // scans the staff contents up to next section break to update the range pitches/tpc's
287 : //---------------------------------------------------------
288 :
289 0 : Ambitus::Ranges Ambitus::estimateRanges() const
290 : {
291 0 : Ambitus::Ranges result;
292 :
293 0 : Segment* s = segment() ? segment()->measure()->findSegment(SegmentType::ChordRest, segment()->tick()) : nullptr;
294 0 : for (; s && !s->measure()->sectionBreak(); s = s->nextCR()) {
295 0 : for (track_idx_t t = track(); t < track() + VOICES; t++) {
296 0 : EngravingItem* e = s->element(t);
297 0 : if (!e || !e->isChord()) {
298 0 : continue;
299 : }
300 0 : Chord* chord = toChord(e);
301 0 : for (Note* n : chord->notes()) {
302 0 : if (!n->play()) {
303 0 : continue;
304 : }
305 0 : int pitch = n->epitch() + n->ottaveCapoFret(); // written pitch, accounting for octave offset
306 0 : if (pitch > result.topPitch) {
307 0 : result.topPitch = pitch;
308 0 : result.topTpc = n->tpc();
309 : }
310 0 : if (pitch < result.bottomPitch) {
311 0 : result.bottomPitch = pitch;
312 0 : result.bottomTpc = n->tpc();
313 : }
314 : }
315 : }
316 : }
317 :
318 0 : return result;
319 : }
320 :
321 0 : void Ambitus::remove(EngravingItem* e)
322 : {
323 0 : if (e->isAccidental()) {
324 : //! NOTE Do nothing (removing _topAccid or _bottomAccid)
325 0 : return;
326 : }
327 :
328 0 : EngravingItem::remove(e);
329 : }
330 :
331 : //---------------------------------------------------------
332 : // getProperty
333 : //---------------------------------------------------------
334 :
335 18 : PropertyValue Ambitus::getProperty(Pid propertyId) const
336 : {
337 18 : switch (propertyId) {
338 0 : case Pid::HEAD_GROUP:
339 0 : return noteHeadGroup();
340 0 : case Pid::HEAD_TYPE:
341 0 : return noteHeadType();
342 0 : case Pid::MIRROR_HEAD:
343 0 : return direction();
344 0 : case Pid::GHOST: // recycled property = _hasLine
345 0 : return hasLine();
346 0 : case Pid::LINE_WIDTH:
347 0 : return lineWidth();
348 0 : case Pid::TPC1:
349 0 : return topTpc();
350 0 : case Pid::FBPARENTHESIS1: // recycled property = _bottomTpc
351 0 : return bottomTpc();
352 0 : case Pid::PITCH:
353 0 : return topPitch();
354 0 : case Pid::FBPARENTHESIS2: // recycled property = _bottomPitch
355 0 : return bottomPitch();
356 0 : case Pid::FBPARENTHESIS3: // recycled property = octave of _topPitch
357 0 : return topOctave();
358 0 : case Pid::FBPARENTHESIS4: // recycled property = octave of _bottomPitch
359 0 : return bottomOctave();
360 18 : default:
361 18 : return EngravingItem::getProperty(propertyId);
362 : }
363 : }
364 :
365 : //---------------------------------------------------------
366 : // setProperty
367 : //---------------------------------------------------------
368 :
369 1 : bool Ambitus::setProperty(Pid propertyId, const PropertyValue& v)
370 : {
371 1 : switch (propertyId) {
372 0 : case Pid::HEAD_GROUP:
373 0 : setNoteHeadGroup(v.value<NoteHeadGroup>());
374 0 : break;
375 0 : case Pid::HEAD_TYPE:
376 0 : setNoteHeadType(v.value<NoteHeadType>());
377 0 : break;
378 1 : case Pid::MIRROR_HEAD:
379 1 : setDirection(v.value<DirectionH>());
380 1 : break;
381 0 : case Pid::GHOST: // recycled property = _hasLine
382 0 : setHasLine(v.toBool());
383 0 : break;
384 0 : case Pid::LINE_WIDTH:
385 0 : setLineWidth(v.value<Spatium>());
386 0 : break;
387 0 : case Pid::TPC1:
388 0 : setTopTpc(v.toInt());
389 0 : break;
390 0 : case Pid::FBPARENTHESIS1: // recycled property = _bottomTpc
391 0 : setBottomTpc(v.toInt());
392 0 : break;
393 0 : case Pid::PITCH:
394 0 : setTopPitch(v.toInt());
395 0 : break;
396 0 : case Pid::FBPARENTHESIS2: // recycled property = _bottomPitch
397 0 : setBottomPitch(v.toInt());
398 0 : break;
399 0 : case Pid::FBPARENTHESIS3: // recycled property = octave of _topPitch
400 0 : setTopPitch(topPitch() % PITCH_DELTA_OCTAVE + (v.toInt() + 1) * PITCH_DELTA_OCTAVE);
401 0 : break;
402 0 : case Pid::FBPARENTHESIS4: // recycled property = octave of _bottomPitch
403 0 : setBottomPitch(bottomPitch() % PITCH_DELTA_OCTAVE + (v.toInt() + 1) * PITCH_DELTA_OCTAVE);
404 0 : break;
405 0 : case Pid::COLOR:
406 : case Pid::VISIBLE:
407 0 : m_topAccidental->setProperty(propertyId, v);
408 0 : m_bottomAccidental->setProperty(propertyId, v);
409 0 : EngravingItem::setProperty(propertyId, v);
410 0 : break;
411 0 : default:
412 0 : return EngravingItem::setProperty(propertyId, v);
413 : }
414 1 : triggerLayout();
415 1 : return true;
416 : }
417 :
418 : //---------------------------------------------------------
419 : // propertyDefault
420 : //---------------------------------------------------------
421 :
422 18 : PropertyValue Ambitus::propertyDefault(Pid id) const
423 : {
424 18 : switch (id) {
425 0 : case Pid::HEAD_GROUP:
426 0 : return NOTEHEADGROUP_DEFAULT;
427 0 : case Pid::HEAD_TYPE:
428 0 : return NOTEHEADTYPE_DEFAULT;
429 0 : case Pid::MIRROR_HEAD:
430 0 : return DIRECTION_DEFAULT;
431 0 : case Pid::GHOST:
432 0 : return HASLINE_DEFAULT;
433 0 : case Pid::LINE_WIDTH:
434 0 : return LINEWIDTH_DEFAULT;
435 0 : case Pid::TPC1:
436 0 : return estimateRanges().topTpc;
437 0 : case Pid::FBPARENTHESIS1:
438 0 : return estimateRanges().bottomTpc;
439 0 : case Pid::PITCH:
440 0 : return estimateRanges().topPitch;
441 0 : case Pid::FBPARENTHESIS2:
442 0 : return estimateRanges().bottomPitch;
443 0 : case Pid::FBPARENTHESIS3:
444 0 : return int(estimateRanges().topPitch / PITCH_DELTA_OCTAVE) - 1;
445 0 : case Pid::FBPARENTHESIS4:
446 0 : return int(estimateRanges().bottomPitch / PITCH_DELTA_OCTAVE) - 1;
447 18 : default:
448 18 : return EngravingItem::propertyDefault(id);
449 : }
450 : //return QVariant();
451 : }
452 :
453 : //---------------------------------------------------------
454 : // nextSegmentElement
455 : //---------------------------------------------------------
456 :
457 0 : EngravingItem* Ambitus::nextSegmentElement()
458 : {
459 0 : return segment()->firstInNextSegments(staffIdx());
460 : }
461 :
462 : //---------------------------------------------------------
463 : // prevSegmentElement
464 : //---------------------------------------------------------
465 :
466 0 : EngravingItem* Ambitus::prevSegmentElement()
467 : {
468 0 : return segment()->lastInPrevSegments(staffIdx());
469 : }
470 :
471 : //---------------------------------------------------------
472 : // accessibleInfo
473 : //---------------------------------------------------------
474 :
475 0 : String Ambitus::accessibleInfo() const
476 : {
477 0 : if (!tpcIsValid(m_topTpc) || !tpcIsValid(m_bottomTpc)) {
478 0 : return EngravingItem::accessibleInfo();
479 : }
480 0 : return EngravingItem::accessibleInfo() + u"; "
481 0 : + muse::mtrc("engraving", "Top pitch: %1; Bottom pitch: %2")
482 0 : .arg(tpc2name(topTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false) + String::number(topOctave()),
483 0 : tpc2name(bottomTpc(), NoteSpellingType::STANDARD, NoteCaseType::AUTO, false) + String::number(bottomOctave()));
484 : }
485 :
486 4 : AccidentalType Ambitus::accidentalType(int tpc, Key key)
487 : {
488 4 : AccidentalType accidType = AccidentalType::NONE;
489 : // if (13 <= (tpc - key) <= 19) there is no accidental)
490 4 : if (tpc - int(key) >= 13 && tpc - int(key) <= 19) {
491 3 : accidType = AccidentalType::NONE;
492 : } else {
493 1 : AccidentalVal accidVal = tpc2alter(tpc);
494 1 : accidType = Accidental::value2subtype(accidVal);
495 1 : if (accidType == AccidentalType::NONE) {
496 0 : accidType = AccidentalType::NATURAL;
497 : }
498 : }
499 :
500 4 : return accidType;
501 : }
502 :
503 : //! TODO Seems to need to be moved to utilities
504 4 : int Ambitus::staffLine(int tpc, int pitch, ClefType clf)
505 : {
506 4 : int line = absStep(tpc, pitch);
507 4 : line = relStep(line, clf);
508 4 : return line;
509 : }
|